1 /* mygtk.c
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 "global.h"
21 #undef _
22 #define _(X) X
23
24 #include "mygtk.h"
25 #include "memory.h"
26 #include "vcode.h"
27 #include "png.h"
28 #include "mainwindow.h"
29 #include "canvas.h"
30 #include "inifile.h"
31
32 #if GTK_MAJOR_VERSION == 1
33 #include <gtk/gtkprivate.h>
34 #endif
35
36 #if (GTK_MAJOR_VERSION == 1) || defined GDK_WINDOWING_X11
37 #include <X11/Xlib.h>
38 #include <X11/Xatom.h>
39 #include <gdk/gdkx.h>
40 #if GTK_MAJOR_VERSION == 3
41 #include <cairo-xlib.h>
42 #endif
43
44 #elif defined GDK_WINDOWING_WIN32
45 #define WIN32_LEAN_AND_MEAN
46 #include <windows.h>
47 #include <gdk/gdkwin32.h>
48 #endif
49
50 GtkWidget *main_window;
51
52 /// GENERIC WIDGET PRIMITIVES
53
54 #if GTK_MAJOR_VERSION == 3
55 #define GtkAdjustment_t GtkAdjustment
56
57 /* I'm totally sick and tired of this "deprecation" game. Deprecate them players
58 * and the scooter they rode in on - WJ */
59 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
60
61 #else
62 #define GtkAdjustment_t GtkObject
63 #endif
64
65 static GtkWidget *spin_new_x(GtkAdjustment_t *adj, int fpart);
66
add_a_window(GtkWindowType type,char * title,GtkWindowPosition pos)67 GtkWidget *add_a_window(GtkWindowType type, char *title, GtkWindowPosition pos)
68 {
69 GtkWidget *win = gtk_window_new(type);
70 gtk_window_set_title(GTK_WINDOW(win), title);
71 gtk_window_set_position(GTK_WINDOW(win), pos);
72
73 return win;
74 }
75
add_a_spin(int value,int min,int max)76 GtkWidget *add_a_spin( int value, int min, int max )
77 {
78 return (spin_new_x(gtk_adjustment_new(value, min, max, 1, 10, 0), 0));
79 }
80
81 // Write UTF-8 text to console
82
console_printf(char * format,...)83 static void console_printf(char *format, ...)
84 {
85 #ifdef WIN32
86 static char codepage[16];
87 #endif
88 va_list args;
89 char *txt, *tx2;
90
91 va_start(args, format);
92 txt = g_strdup_vprintf(format, args);
93 va_end(args);
94 #if GTK_MAJOR_VERSION == 1
95 /* Same encoding as console */
96 fputs(txt, stdout);
97 #else /* if GTK_MAJOR_VERSION >= 2 */
98 /* UTF-8 */
99 #ifdef WIN32
100 if (!codepage[0]) sprintf(codepage, "cp%d", GetConsoleCP());
101 /* !!! Iconv on Windows knows "utf-8" but no "utf8" */
102 tx2 = g_convert_with_fallback(txt, -1, codepage, "utf-8", "?",
103 NULL, NULL, NULL);
104 #else
105 tx2 = g_locale_from_utf8(txt, -1, NULL, NULL, NULL);
106 #endif
107 fputs(tx2, stdout);
108 g_free(tx2);
109 #endif
110 g_free(txt);
111 }
112
113
114 int user_break;
115
116 //// PROGRESS WINDOW
117
118 static void **progress_window;
119
120 typedef struct {
121 int stop, can_stop;
122 char *what;
123 void **pbar;
124 } progress_dd;
125
do_cancel_progress(progress_dd * dt)126 static void do_cancel_progress(progress_dd *dt)
127 {
128 dt->stop = 1;
129 user_break = TRUE;
130 }
131
delete_progress()132 static void delete_progress()
133 {
134 // This stops the user closing the window via the window manager
135 }
136
137 #define WBbase progress_dd
138 static void *progress_code[] = {
139 WIDTH(400), WINDOWm(_("Please Wait ...")),
140 EVENT(CANCEL, delete_progress),
141 BORDER(FRAME, 0),
142 EFVBOX, // originally was box in viewport
143 REF(pbar), PROGRESSp(what),
144 IFx(can_stop, 1),
145 HSEP,
146 BUTTON(_("STOP"), do_cancel_progress),
147 ENDIF(1),
148 WSHOW
149 };
150 #undef WBbase
151
152 /* Print stars for a progress indicator */
153 #define STARS_IN_ROW 20
add_stars(double val)154 static void add_stars(double val)
155 {
156 int i, l, n = rint(val * STARS_IN_ROW) + 1;
157 for (i = l = (int)progress_window; i < n; i++) putc('*', stdout);
158 if (l < n) fflush(stdout);
159 progress_window = (void *)n;
160 }
161
progress_init(char * text,int canc)162 void progress_init(char *text, int canc) // Initialise progress window
163 {
164 progress_dd tdata = { 0, canc, text };
165
166 if (cmd_mode) // Console
167 {
168 console_printf("%s - %s\n", __(text), __("Please Wait ..."));
169 progress_window = (void *)(1 + 0);
170 return;
171 }
172 // GUI
173
174 /* Break pointer grabs, to avoid originating widget misbehaving later on */
175 release_grab();
176 update_stuff(CF_NOW);
177
178 progress_window = run_create(progress_code, &tdata, sizeof(tdata));
179
180 progress_update(0.0);
181 }
182
progress_update(float val)183 int progress_update(float val) // Update progress window
184 {
185 if (!progress_window);
186 else if (cmd_mode) add_stars(val); // Console
187 else // GUI
188 {
189 progress_dd *dt = GET_DDATA(progress_window);
190 cmd_setv(dt->pbar, (void *)(int)(val * 100), PROGRESS_PERCENT);
191 handle_events();
192 // !!! Depends on window not being closeable by user
193 return (dt->stop);
194 }
195 return (FALSE);
196 }
197
progress_end()198 void progress_end() // Close progress window
199 {
200 if (!progress_window);
201 else if (cmd_mode) // Console
202 {
203 add_stars(1.0);
204 putc('\n', stdout);
205 }
206 // GUI
207 else run_destroy(progress_window);
208 progress_window = NULL;
209 }
210
211
212
213 //// ALERT BOX
214
215 /* !!! Only up to 2 choices for now */
216 typedef struct {
217 char *title, *what;
218 char *cancel, *ok;
219 int have2;
220 void **cb, **res;
221 } alert_dd;
222
223 #define WBbase alert_dd
224 static void *alert_code[] = {
225 DIALOGpm(title),
226 BORDER(LABEL, 8),
227 WLABELp(what),
228 WDONE, // vbox
229 BORDER(BUTTON, 2),
230 REF(cb), CANCELBTNp(cancel, dialog_event),
231 IF(have2), BUTTONp(ok, dialog_event),
232 RAISED, WDIALOG(res)
233 };
234 #undef WBbase
235
alert_box(char * title,char * message,char * text1,...)236 int alert_box(char *title, char *message, char *text1, ...)
237 {
238 alert_dd *dt, tdata = { title, message, _("OK"), NULL, 0 };
239 va_list args;
240 char *txt;
241 void **dd;
242 int res, aok = FALSE;
243
244 // Empty string here means, "OK" is ok: no choice but no error
245 if (text1 && !(aok = !text1[0]))
246 {
247 tdata.cancel = text1;
248 va_start(args, text1);
249 if ((txt = va_arg(args, char *)))
250 {
251 tdata.ok = txt;
252 tdata.have2 = TRUE;
253 }
254 va_end(args);
255 }
256
257 if (cmd_mode) // Console
258 {
259 console_printf("%s\n[ %s ]\n", __(message),
260 __(tdata.ok ? tdata.ok : tdata.cancel));
261 res = tdata.ok ? 2 : 1; /* Assume "yes" in commandline mode */
262 }
263 else // GUI
264 {
265 update_stuff(CF_NOW);
266
267 dd = run_create(alert_code, &tdata, sizeof(tdata)); // run dialog
268 dt = GET_DDATA(dd);
269 res = origin_slot(dt->res) == dt->cb ? 1 : 2;
270 run_destroy(dd);
271 }
272
273 if (aok) res = 2;
274 if (res == 1) user_break = TRUE;
275 return (res);
276 }
277
278 // Tablet handling
279
280 #if GTK_MAJOR_VERSION == 1
281 GdkDeviceInfo *tablet_device;
282 #else /* #if GTK_MAJOR_VERSION >= 2 */
283 GdkDevice *tablet_device;
284 #endif
285
init_tablet()286 void init_tablet()
287 {
288 GList *devs;
289 char *name, buf[64];
290 int i, n, mode;
291
292 /* Do nothing if tablet wasn't working the last time */
293 if (!inifile_get_gboolean("tablet_USE", FALSE)) return;
294
295 name = inifile_get("tablet_name", "?");
296 mode = inifile_get_gint32("tablet_mode", 0);
297
298 #if GTK_MAJOR_VERSION == 1
299 for (devs = gdk_input_list_devices(); devs; devs = devs->next)
300 {
301 GdkDeviceInfo *device = devs->data;
302 GdkAxisUse *u;
303
304 if (strcmp(device->name, name)) continue;
305 /* Found the one that was working the last time */
306 tablet_device = device;
307 gdk_input_set_mode(device->deviceid, mode);
308 n = device->num_axes;
309 u = calloc(n, sizeof(*u));
310 for (i = 0; i < n; i++)
311 {
312 sprintf(buf, "tablet_axes_v%d", i);
313 u[i] = inifile_get_gint32(buf, GDK_AXIS_IGNORE);
314 }
315 gdk_input_set_axes(device->deviceid, u);
316 free(u);
317 break;
318 }
319 #else /* #if GTK_MAJOR_VERSION >= 2 */
320 #if GTK_MAJOR_VERSION == 3
321 devs = gdk_device_manager_list_devices(gdk_display_get_device_manager(
322 gdk_display_get_default()), GDK_DEVICE_TYPE_SLAVE);
323 #else
324 devs = gdk_devices_list();
325 #endif
326 for (; devs; devs = devs->next)
327 {
328 GdkDevice *device = devs->data;
329
330 if (strcmp(gdk_device_get_name(device), name)) continue;
331 /* Found the one that was working the last time */
332 tablet_device = device;
333 gdk_device_set_mode(device, mode);
334 n = gdk_device_get_n_axes(device);
335 for (i = 0; i < n; i++)
336 {
337 sprintf(buf, "tablet_axes_v%d", i);
338 gdk_device_set_axis_use(device, i,
339 inifile_get_gint32(buf, GDK_AXIS_IGNORE));
340 }
341 break;
342 }
343 #endif
344
345 inifile_set_gboolean("tablet_USE", !!tablet_device);
346 }
347
348 // TABLETBTN widget
349
350 static void **tablet_slot;
351 static void *tablet_dlg;
352
353 #if GTK_MAJOR_VERSION == 3
354
355 #define MAX_AXES 128 /* Unlikely to exist, & too long a list to show this many */
356
357 typedef struct {
358 int dev;
359 int mode; // GDK_MODE_DISABLED / GDK_MODE_SCREEN / GDK_MODE_WINDOW
360 int ax[7], ax0[7]; // GDK_AXIS_IGNORE .. GDK_AXIS_WHEEL
361 int lock;
362 char **devnames, **axes;
363 GdkDevice **devices;
364 char *xtra;
365 void **group, **use[7];
366 } tablet_dd;
367
368 #endif
369
370 #if GTK_MAJOR_VERSION == 1
371
tablet_find(gint deviceid)372 static GdkDeviceInfo *tablet_find(gint deviceid)
373 {
374 GList *devs;
375
376 for (devs = gdk_input_list_devices(); devs; devs = devs->next)
377 {
378 GdkDeviceInfo *device = devs->data;
379 if (device->deviceid == deviceid) return (device);
380 }
381 return (NULL);
382 }
383
384 #endif
385
conf_done(void * cause)386 void conf_done(void *cause)
387 {
388 char buf[64];
389 int i, n;
390
391 if (!tablet_slot) return;
392
393 /* Use last selected device if it's active */
394 {
395 #if GTK_MAJOR_VERSION == 1
396 GdkDeviceInfo *dev = tablet_find(GTK_INPUT_DIALOG(tablet_dlg)->current_device);
397 #elif GTK_MAJOR_VERSION == 2
398 GdkDevice *dev = GTK_INPUT_DIALOG(tablet_dlg)->current_device;
399 #else /* #if GTK_MAJOR_VERSION == 3 */
400 tablet_dd *dt = GET_DDATA((void **)tablet_dlg);
401 GdkDevice *dev = dt->devices[dt->dev];
402 #endif
403 if (dev && (gdk_device_get_mode(dev) != GDK_MODE_DISABLED))
404 {
405 tablet_device = dev;
406 // Skip event if within do_destroy()
407 if (cause) cmd_event(tablet_slot, op_EVT_CHANGE);
408 }
409 }
410
411 if (tablet_device)
412 {
413 inifile_set("tablet_name", (char *)gdk_device_get_name(tablet_device));
414 inifile_set_gint32("tablet_mode", gdk_device_get_mode(tablet_device));
415
416 n = gdk_device_get_n_axes(tablet_device);
417 for (i = 0; i < n; i++)
418 {
419 sprintf(buf, "tablet_axes_v%d", i);
420 inifile_set_gint32(buf,
421 #if GTK_MAJOR_VERSION == 1
422 tablet_device->axes[i]);
423 #elif GTK_MAJOR_VERSION == 2
424 tablet_device->axes[i].use);
425 #else /* #if GTK_MAJOR_VERSION == 3 */
426 gdk_device_get_axis_use(tablet_device, i));
427 #endif
428 }
429 }
430 inifile_set_gboolean("tablet_USE", !!tablet_device);
431
432 #if GTK_MAJOR_VERSION == 3
433 run_destroy(tablet_dlg);
434 #else
435 gtk_widget_destroy(tablet_dlg);
436 #endif
437 tablet_slot = NULL;
438 }
439
440 #if GTK_MAJOR_VERSION == 3
441
tablet_changed(tablet_dd * dt,void ** wdata,int what,void ** where)442 static void tablet_changed(tablet_dd *dt, void **wdata, int what, void **where)
443 {
444 int i, n, u, u0, axis, *cause = cmd_read(where, dt);
445 GdkDevice *dev = dt->devices[dt->dev];
446 GList *ids, *id;
447
448
449 if (dt->lock) return;
450 dt->lock++;
451
452 /* Select device */
453 if (cause == &dt->dev)
454 {
455 dt->mode = gdk_device_get_mode(dev);
456 memset(dt->ax, 0, sizeof(dt->ax)); // Clear axis use
457 n = gdk_device_get_n_axes(dev);
458 ids = id = gdk_device_list_axes(dev);
459 if (n > MAX_AXES) n = MAX_AXES; // Paranoia
460 for (i = 0; i < n; i++ , id = id->next)
461 {
462 /* Put axis name into list */
463 dt->axes[i + 1] = gdk_atom_name(GDK_POINTER_TO_ATOM(id->data));
464 /* Attach to use or not-use */
465 u = gdk_device_get_axis_use(dev, i);
466 dt->ax[u] = i + 1;
467 }
468 dt->axes[n + 1] = NULL;
469 g_list_free(ids);
470
471 /* Display all that */
472 cmd_reset(dt->group, dt);
473 }
474 /* Change mode */
475 else if (cause == &dt->mode)
476 {
477 if (!gdk_device_set_mode(dev, dt->mode))
478 /* Display actual mode if failed to set */
479 cmd_set(origin_slot(where), gdk_device_get_mode(dev));
480 else /* Report the change */
481 {
482 tablet_device = dt->mode == GDK_MODE_DISABLED ? NULL : dev;
483 cmd_event(tablet_slot, op_EVT_CHANGE);
484 }
485 }
486 /* Change axis use */
487 else
488 {
489 u = cause - dt->ax; // Use
490 axis = dt->ax[u];
491 u0 = axis ? gdk_device_get_axis_use(dev, axis - 1) : 0;
492 if (u0) cmd_set(dt->use[u0], 0); // Steal from other use
493 /* Previous axis, if there was one, becomes unused */
494 if (dt->ax0[u]) gdk_device_set_axis_use(dev, dt->ax0[u] - 1, 0);
495 /* The new one gets new use */
496 gdk_device_set_axis_use(dev, axis - 1, u);
497 }
498
499 memcpy(dt->ax0, dt->ax, sizeof(dt->ax)); // Save current state
500
501 dt->lock--;
502 }
503
504 #undef _
505 #define _(X) X
506
507 static char *input_modes[3] = { _("Disabled"), _("Screen"), _("Window") };
508
509 #define WBbase tablet_dd
510 static void *tablet_code[] = {
511 WINDOW(_("Input")), // nonmodal
512 EVENT(DESTROY, conf_done),
513 MKSHRINK, // shrinkable
514 HBOX,
515 MLABEL(_("Device:")), XOPTDe(devnames, dev, tablet_changed), TRIGGER,
516 REF(group), GROUPR,
517 MLABEL(_("Mode:")), OPTe(input_modes, 3, mode, tablet_changed),
518 WDONE,
519 BORDER(NBOOK, 0),
520 NBOOK,
521 BORDER(TABLE, 10),
522 PAGE(_("Axes")),
523 BORDER(SCROLL, 0),
524 WANTMAX, // max size
525 XSCROLL(1, 1), // auto/auto
526 TABLE2(6),
527 REF(use[1]), TOPTDe(_("X:"), axes, ax[1], tablet_changed),
528 REF(use[2]), TOPTDe(_("Y:"), axes, ax[2], tablet_changed),
529 REF(use[3]), TOPTDe(_("Pressure:"), axes, ax[3], tablet_changed),
530 REF(use[4]), TOPTDe(_("X tilt:"), axes, ax[4], tablet_changed),
531 REF(use[5]), TOPTDe(_("Y tilt:"), axes, ax[5], tablet_changed),
532 REF(use[6]), TOPTDe(_("Wheel:"), axes, ax[6], tablet_changed),
533 WDONE, // table
534 WDONE, // page
535 WDONE, // nbook
536 HBOX,
537 DEFBORDER(BUTTON),
538 OKBTN(_("OK"), conf_done),
539 CLEANUP(xtra),
540 WSHOW
541 };
542 #undef WBbase
543
544 #undef _
545 #define _(X) __(X)
546
conf_tablet(void ** slot)547 void conf_tablet(void **slot)
548 {
549 tablet_dd tdata;
550 GList *devs, *d;
551 int n, i;
552
553 if (tablet_slot) return; // There can be only one
554 tablet_slot = slot;
555
556 memset(&tdata, 0, sizeof(tdata));
557 devs = gdk_device_manager_list_devices(gdk_display_get_device_manager(
558 gdk_display_get_default()), GDK_DEVICE_TYPE_SLAVE);
559 n = g_list_length(devs);
560 tdata.xtra = multialloc(MA_ALIGN_DEFAULT,
561 &tdata.devices, sizeof(GdkDevice *) * n,
562 &tdata.devnames, sizeof(char *) * (n + 1),
563 &tdata.axes, sizeof(char *) * (MAX_AXES + 1),
564 NULL);
565
566 /* Store devices as array */
567 devs = g_list_reverse(devs); // To get GTK+2-like ordering
568 for (i = 0 , d = devs; d; d = d->next)
569 {
570 /* Skip keyboards */
571 if (gdk_device_get_source(d->data) == GDK_SOURCE_KEYBOARD)
572 continue;
573 tdata.devices[i] = d->data;
574 tdata.devnames[i] = (char *)gdk_device_get_name(d->data);
575 i++;
576 }
577 tdata.devnames[i] = NULL;
578 g_list_free(devs);
579
580 tdata.axes[0] = _("none");
581
582 tablet_dlg = run_create(tablet_code, &tdata, sizeof(tdata));
583 }
584
585 #else
586
587 /* Use GtkInputDialog on GTK+1&2 */
588 #if GTK_MAJOR_VERSION == 1
589
tablet_toggle(GtkInputDialog * inputdialog,gint deviceid,gpointer user_data)590 static void tablet_toggle(GtkInputDialog *inputdialog, gint deviceid,
591 gpointer user_data)
592 {
593 GdkDeviceInfo *dev = tablet_find(deviceid);
594 tablet_device = !dev || (dev->mode == GDK_MODE_DISABLED) ? NULL : dev;
595 cmd_event(user_data, op_EVT_CHANGE);
596 }
597
598 #else /* #if GTK_MAJOR_VERSION == 2 */
599
tablet_toggle(GtkInputDialog * inputdialog,GdkDevice * deviceid,gpointer user_data)600 static void tablet_toggle(GtkInputDialog *inputdialog, GdkDevice *deviceid,
601 gpointer user_data)
602 {
603 tablet_device = gdk_device_get_mode(deviceid) == GDK_MODE_DISABLED ?
604 NULL : deviceid;
605 cmd_event(user_data, op_EVT_CHANGE);
606 }
607
608 #endif
609
conf_del(GtkWidget * widget)610 static gboolean conf_del(GtkWidget *widget)
611 {
612 conf_done(widget);
613 return (TRUE);
614 }
615
conf_tablet(void ** slot)616 void conf_tablet(void **slot)
617 {
618 GtkWidget *inputd;
619 GtkInputDialog *inp;
620 GtkAccelGroup *ag;
621
622 if (tablet_slot) return; // There can be only one
623 tablet_slot = slot;
624 tablet_dlg = inputd = gtk_input_dialog_new();
625 gtk_window_set_position(GTK_WINDOW(inputd), GTK_WIN_POS_CENTER);
626 inp = GTK_INPUT_DIALOG(inputd);
627
628 ag = gtk_accel_group_new();
629 gtk_signal_connect(GTK_OBJECT(inp->close_button), "clicked",
630 GTK_SIGNAL_FUNC(conf_done), NULL);
631 gtk_widget_add_accelerator(inp->close_button, "clicked", ag,
632 GDK_Escape, 0, (GtkAccelFlags)0);
633 gtk_signal_connect(GTK_OBJECT(inputd), "delete_event",
634 GTK_SIGNAL_FUNC(conf_del), NULL);
635
636 gtk_signal_connect(GTK_OBJECT(inputd), "enable-device",
637 GTK_SIGNAL_FUNC(tablet_toggle), slot);
638 gtk_signal_connect(GTK_OBJECT(inputd), "disable-device",
639 GTK_SIGNAL_FUNC(tablet_toggle), slot);
640
641 if (inp->keys_list) gtk_widget_hide(inp->keys_list);
642 if (inp->keys_listbox) gtk_widget_hide(inp->keys_listbox);
643 gtk_widget_hide(inp->save_button);
644
645 gtk_window_add_accel_group(GTK_WINDOW(inputd), ag);
646 gtk_widget_show(inputd);
647 }
648
649 #endif /* GTK+1&2 */
650
651 // Slider-spin combo (a decorated spinbutton)
652
mt_spinslide_new(int swidth,int sheight)653 GtkWidget *mt_spinslide_new(int swidth, int sheight)
654 {
655 GtkWidget *box, *slider, *spin;
656 GtkAdjustment_t *adj = gtk_adjustment_new(0, 0, 1, 1, 10, 0);
657 #if GTK_MAJOR_VERSION == 3
658 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
659
660 slider = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(adj));
661 gtk_widget_set_size_request(slider, swidth, sheight);
662 #else
663 box = gtk_hbox_new(FALSE, 0);
664
665 slider = gtk_hscale_new(GTK_ADJUSTMENT(adj));
666 gtk_widget_set_usize(slider, swidth, sheight);
667 #endif
668 gtk_box_pack_start(GTK_BOX(box), slider, swidth < 0, TRUE, 0);
669 gtk_scale_set_draw_value(GTK_SCALE(slider), FALSE);
670 gtk_scale_set_digits(GTK_SCALE(slider), 0);
671
672 spin = spin_new_x(adj, 0);
673 gtk_box_pack_start(GTK_BOX(box), spin, swidth >= 0, TRUE, 2);
674
675 gtk_widget_show_all(box);
676 return (spin);
677 }
678
679 // GTK+3 specific support code
680
681 #if GTK_MAJOR_VERSION == 3
682
cairo_surface_fdestroy(cairo_surface_t * s)683 void cairo_surface_fdestroy(cairo_surface_t *s)
684 {
685 cairo_surface_flush(s); // In case it's actually needed
686 cairo_surface_destroy(s);
687 }
688
cairo_upload_rgb(cairo_surface_t * ref,GdkWindow * win,unsigned char * src,int w,int h,int len)689 cairo_surface_t *cairo_upload_rgb(cairo_surface_t *ref, GdkWindow *win,
690 unsigned char *src, int w, int h, int len)
691 {
692 cairo_surface_t *s;
693 unsigned char *dst0;
694 int i, n, st;
695
696 if (ref) s = cairo_surface_create_similar_image(ref, CAIRO_FORMAT_RGB24, w, h);
697 else s = gdk_window_create_similar_image_surface(win, CAIRO_FORMAT_RGB24, w, h, 1);
698 // !!! See below CAIRO_FORMAT_ARGB32 // !!! But not for exported pixmap
699 cairo_surface_flush(s);
700 dst0 = cairo_image_surface_get_data(s);
701 st = cairo_image_surface_get_stride(s);
702 len -= w * 3;
703 for (i = h; i-- > 0; dst0 += st , src += len)
704 {
705 guint32 *dest = (void *)dst0;
706 for (n = w; n-- > 0; src += 3) *dest++ = MEM_2_INT(src, 0);
707 // !!! Maybe need to upconvert to ARGB (by OR 0xFF000000U), at least for Windows:
708 // see Cairo win32/cairo-win32-display-surface.c
709 // for (n = w; n-- > 0; src += 3) *dest++ = MEM_2_INT(src, 0) | 0xFF000000U;
710 }
711 cairo_surface_mark_dirty(s);
712 return (s);
713 }
714
cairo_set_rgb(cairo_t * cr,int c)715 void cairo_set_rgb(cairo_t *cr, int c)
716 {
717 cairo_set_source_rgb(cr, INT_2_R(c) / 255.0, INT_2_G(c) / 255.0,
718 INT_2_B(c) / 255.0);
719 }
720
721 /* Prevent color bleed on HiDPI */
cairo_unfilter(cairo_t * cr)722 void cairo_unfilter(cairo_t *cr)
723 {
724 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
725 }
726
css_restyle(GtkWidget * w,char * css,char * class,char * name)727 void css_restyle(GtkWidget *w, char *css, char *class, char *name)
728 {
729 static GData *d;
730 static int dset;
731 GQuark q = g_quark_from_string(css);
732 GtkCssProvider *p;
733 GtkStyleContext *c;
734
735 if (!dset) g_datalist_init(&d); // First time
736 dset = TRUE;
737 p = g_datalist_id_get_data(&d, q);
738 if (!p)
739 {
740 p = gtk_css_provider_new();
741 gtk_css_provider_load_from_data(p, css, -1, NULL);
742 g_datalist_id_set_data(&d, q, p);
743 }
744 c = gtk_widget_get_style_context(w);
745 gtk_style_context_add_provider(c, GTK_STYLE_PROVIDER(p),
746 GTK_STYLE_PROVIDER_PRIORITY_USER);
747 if (class) gtk_style_context_add_class(c, class);
748 if (name) g_object_set(w, "name", name, NULL);
749 }
750
add_css_class(GtkWidget * w,char * class)751 void add_css_class(GtkWidget *w, char *class)
752 {
753 gtk_style_context_add_class(gtk_widget_get_style_context(w), class);
754 }
755
756 /* Add CSS, builtin and user-provided, to default screen */
init_css(char * cssfile)757 void init_css(char *cssfile)
758 {
759 GdkScreen *scr = gdk_display_get_default_screen(gdk_display_get_default());
760 GtkCssProvider *p;
761 GtkIconTheme *theme;
762 char *s, *cp = NULL;
763
764 /* GTK+ 3.20 switched from classes to "CSS nodes", and added "min-width" &
765 * "min-height" style properties, themed to some crazy values - WJ */
766 if (gtk3version >= 20) // Make entries same height as buttons
767 {
768 GtkWidget *btn = gtk_button_new();
769 GtkStyleContext *ctx = gtk_widget_get_style_context(btn);
770 gint n;
771
772 gtk_style_context_get(ctx, gtk_style_context_get_state(ctx),
773 "min-height", &n, NULL);
774 cp = g_strdup_printf("spinbutton, entry { min-height:%dpx; }", n);
775 g_object_ref_sink(btn);
776 g_object_unref(btn);
777 }
778 s = g_strconcat((gtk3version < 20 ? ".spinbutton *,.grid-child" :
779 "spinbutton button,flowboxchild"),
780 " { padding:0; }"
781 ".image-button { padding:4px; }"
782 ".wjpixmap { padding:4px; outline-offset:-1px; }"
783 ".mtPaint_gradbar_button { padding:4px; }",
784 (gtk3version < 20 ? "" :
785 ".mtPaint_gradbar_button { min-width:0; min-height:0; }"),
786 cp, NULL);
787 p = gtk_css_provider_new();
788 gtk_css_provider_load_from_data(p, s, -1, NULL);
789 gtk_style_context_add_provider_for_screen(scr, GTK_STYLE_PROVIDER(p),
790 GTK_STYLE_PROVIDER_PRIORITY_USER);
791 g_free(s);
792 g_free(cp);
793
794 if (!cssfile || !cssfile[0]) return;
795
796 /* Load user-provided CSS */
797 p = gtk_css_provider_new();
798 gtk_css_provider_load_from_path(p, cssfile, NULL);
799 gtk_style_context_add_provider_for_screen(scr, GTK_STYLE_PROVIDER(p),
800 GTK_STYLE_PROVIDER_PRIORITY_USER + 100);
801
802 /* Make the directory with it the first search dir for icons */
803 s = resolve_path(NULL, 0, cssfile);
804 cp = strrchr(s, DIR_SEP);
805 theme = gtk_icon_theme_get_for_screen(scr);
806 if (cp && theme) // Paranoia
807 {
808 *cp = '\0'; // Cut off filename
809 gtk_icon_theme_prepend_search_path(theme, s);
810 }
811 free(s);
812 }
813
combobox_scan(GtkWidget * widget,gpointer data)814 static void combobox_scan(GtkWidget *widget, gpointer data)
815 {
816 GtkWidget **scan = data;
817 if (GTK_IS_BOX(widget)) scan[1] = widget;
818 else if (GTK_IS_BUTTON(widget)) scan[0] = widget;
819 }
820
821 /* Find button widget of a GtkComboBox with entry */
combobox_button(GtkWidget * cbox)822 GtkWidget *combobox_button(GtkWidget *cbox)
823 {
824 GtkWidget *scan[2] = { NULL, NULL };
825 gtk_container_forall(GTK_CONTAINER(cbox), combobox_scan, scan);
826 if (!scan[0] && scan[1]) // Structure changed after 3.18
827 gtk_container_forall(GTK_CONTAINER(scan[1]), combobox_scan, scan);
828 return (scan[0]);
829 }
830
831 static GQuark radio_key;
832
833 /* Properties for GtkScrollable */
834 static char *scroll_pnames[] = { NULL, "hadjustment", "vadjustment",
835 "hscroll-policy", "vscroll-policy" };
836 enum {
837 P_HADJ = 1,
838 P_VADJ,
839 P_HSCP,
840 P_VSCP
841 };
842
get_padding_and_border(GtkStyleContext * ctx,GtkBorder * pad,GtkBorder * bor,GtkBorder * both)843 void get_padding_and_border(GtkStyleContext *ctx, GtkBorder *pad, GtkBorder *bor,
844 GtkBorder *both)
845 {
846 GtkStateFlags state = gtk_style_context_get_state(ctx);
847 GtkBorder tmp;
848
849 if (both)
850 {
851 if (!pad) pad = &tmp;
852 if (!bor) bor = both;
853 }
854 if (pad) gtk_style_context_get_padding(ctx, state, pad); // ~ xthickness
855 if (bor) gtk_style_context_get_border(ctx, state, bor);
856 if (both)
857 {
858 both->left = pad->left + bor->left;
859 both->right = pad->right + bor->right;
860 both->top = pad->top + bor->top;
861 both->bottom = pad->bottom + bor->bottom;
862 }
863 }
864
865 #endif
866
867 // Managing batches of radio buttons with minimum of fuss
868
869 /* void handler(GtkWidget *btn, gpointer user_data); */
wj_radio_pack(char ** names,int cnt,int vnum,int idx,void ** r,GtkSignalFunc handler)870 GtkWidget *wj_radio_pack(char **names, int cnt, int vnum, int idx, void **r,
871 GtkSignalFunc handler)
872 {
873 int i, j, x;
874 GtkWidget *table, *button = NULL;
875
876 #if GTK_MAJOR_VERSION == 3
877 radio_key = g_quark_from_static_string("mtPaint.radio");
878 table = gtk_grid_new();
879 gtk_widget_set_hexpand(table, FALSE); // No "inheriting" it from buttons
880 #else
881 table = gtk_table_new(1, 1, FALSE);
882 #endif
883 for (i = j = x = 0; (i != cnt) && names[i]; i++)
884 {
885 if (!names[i][0]) continue;
886 button = gtk_radio_button_new_with_label_from_widget(
887 GTK_RADIO_BUTTON_0(button), __(names[i]));
888 if (vnum > 0) x = j / vnum;
889 #if GTK_MAJOR_VERSION == 3
890 g_object_set_qdata(G_OBJECT(button), radio_key, (gpointer)i);
891 /* Adjusted to account for GTK+3 adding more padding */
892 gtk_container_set_border_width(GTK_CONTAINER(button), 2);
893 gtk_grid_attach(GTK_GRID(table), button, x, j - x * vnum, 1, 1);
894 if (vnum != 1) gtk_widget_set_hexpand(button, TRUE);
895 #else
896 gtk_object_set_user_data(GTK_OBJECT(button), (gpointer)i);
897 gtk_container_set_border_width(GTK_CONTAINER(button), 5);
898 gtk_table_attach(GTK_TABLE(table), button, x, x + 1,
899 j - x * vnum, j - x * vnum + 1,
900 vnum != 1 ? GTK_EXPAND | GTK_FILL : GTK_FILL, 0, 0, 0);
901 #endif
902 if (i == idx) gtk_toggle_button_set_active(
903 GTK_TOGGLE_BUTTON(button), TRUE);
904 if (handler) gtk_signal_connect(GTK_OBJECT(button), "toggled",
905 handler, r);
906 j++;
907 }
908 gtk_widget_show_all(table);
909
910 return (table);
911 }
912
913 #if GTK_MAJOR_VERSION == 3
914
wj_radio_pack_get_active(GtkWidget * widget)915 int wj_radio_pack_get_active(GtkWidget *widget)
916 {
917 GList *curr, *ch = gtk_container_get_children(GTK_CONTAINER(widget));
918 int res = 0;
919
920 for (curr = ch; curr; curr = curr->next)
921 {
922 widget = curr->data;
923 if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) continue;
924 res = (int)g_object_get_qdata(G_OBJECT(widget), radio_key);
925 break;
926 }
927 g_list_free(ch);
928 return (res);
929 }
930
931 #else
932
wj_radio_pack_get_active(GtkWidget * widget)933 int wj_radio_pack_get_active(GtkWidget *widget)
934 {
935 GList *curr;
936
937 for (curr = GTK_TABLE(widget)->children; curr; curr = curr->next)
938 {
939 widget = ((GtkTableChild *)curr->data)->widget;
940 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
941 return ((int)gtk_object_get_user_data(GTK_OBJECT(widget)));
942 }
943 return (0);
944 }
945
946 #endif
947
948 // Easier way with spinbuttons
949
read_spin(GtkWidget * spin)950 int read_spin(GtkWidget *spin)
951 {
952 /* Needed in GTK+2 for late changes */
953 gtk_spin_button_update(GTK_SPIN_BUTTON(spin));
954 return (gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin)));
955 }
956
read_float_spin(GtkWidget * spin)957 double read_float_spin(GtkWidget *spin)
958 {
959 /* Needed in GTK+2 for late changes */
960 gtk_spin_button_update(GTK_SPIN_BUTTON(spin));
961 #if GTK_MAJOR_VERSION == 3
962 return (gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)));
963 #else
964 return (GTK_SPIN_BUTTON(spin)->adjustment->value);
965 #endif
966 }
967
968 #if (GTK_MAJOR_VERSION == 1) && !defined(U_MTK)
969
970 #define MIN_SPIN_BUTTON_WIDTH 30
971
972 /* More-or-less correctly evaluate spinbutton size */
spin_size_req(GtkWidget * widget,GtkRequisition * requisition,gpointer user_data)973 static void spin_size_req(GtkWidget *widget, GtkRequisition *requisition,
974 gpointer user_data)
975 {
976 GtkSpinButton *spin = GTK_SPIN_BUTTON(widget);
977 char num[128];
978 int l1, l2;
979
980 num[0] = '0';
981 sprintf(num + 1, "%.*f", spin->digits, spin->adjustment->lower);
982 l1 = gdk_string_width(widget->style->font, num);
983 sprintf(num + 1, "%.*f", spin->digits, spin->adjustment->upper);
984 l2 = gdk_string_width(widget->style->font, num);
985 if (l1 < l2) l1 = l2;
986 if (l1 > MIN_SPIN_BUTTON_WIDTH)
987 requisition->width += l1 - MIN_SPIN_BUTTON_WIDTH;
988 }
989
990 #endif
991
spin_new_x(GtkAdjustment_t * adj,int fpart)992 static GtkWidget *spin_new_x(GtkAdjustment_t *adj, int fpart)
993 {
994 GtkWidget *spin = gtk_spin_button_new(GTK_ADJUSTMENT(adj), 1, fpart);
995 #if (GTK_MAJOR_VERSION == 1) && !defined(U_MTK)
996 gtk_signal_connect(GTK_OBJECT(spin), "size_request",
997 GTK_SIGNAL_FUNC(spin_size_req), NULL);
998 #endif
999 gtk_widget_show(spin);
1000 gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(spin), TRUE);
1001 return (spin);
1002 }
1003
add_float_spin(double value,double min,double max)1004 GtkWidget *add_float_spin(double value, double min, double max)
1005 {
1006 return (spin_new_x(gtk_adjustment_new(value, min, max, 1, 10, 0), 2));
1007 }
1008
1009 /* void handler(GtkAdjustment *adjustment, gpointer user_data); */
spin_connect(GtkWidget * spin,GtkSignalFunc handler,gpointer user_data)1010 void spin_connect(GtkWidget *spin, GtkSignalFunc handler, gpointer user_data)
1011 {
1012 GtkAdjustment *adj;
1013
1014 adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(spin));
1015 gtk_signal_connect(GTK_OBJECT(adj), "value_changed", handler, user_data);
1016 }
1017
1018 #if GTK_MAJOR_VERSION == 1
1019
spin_set_range(GtkWidget * spin,int min,int max)1020 void spin_set_range(GtkWidget *spin, int min, int max)
1021 {
1022 GtkAdjustment *adj = GTK_SPIN_BUTTON(spin)->adjustment;
1023
1024 adj->lower = min;
1025 adj->upper = max;
1026 gtk_adjustment_set_value(adj, adj->value);
1027 gtk_adjustment_changed(adj);
1028 }
1029
1030 #endif
1031
1032 // Wrapper for utf8->C and C->utf8 translation
1033
gtkxncpy(char * dest,const char * src,int cnt,int u)1034 char *gtkxncpy(char *dest, const char *src, int cnt, int u)
1035 {
1036 #if GTK_MAJOR_VERSION >= 2
1037 char *c = (u ? g_locale_to_utf8 : g_locale_from_utf8)((gchar *)src, -1,
1038 NULL, NULL, NULL);
1039 if (c)
1040 {
1041 if (!dest) return (c);
1042 g_strlcpy(dest, c, cnt);
1043 g_free(c);
1044 }
1045 else
1046 #endif
1047 {
1048 if (!dest) return (g_strdup(src));
1049 u = strlen(src);
1050 if (u >= cnt) u = cnt - 1;
1051 /* Allow for overlapping buffers */
1052 memmove(dest, src, u);
1053 dest[u] = 0;
1054 }
1055 return (dest);
1056 }
1057
1058 // A more sane replacement for strncat()
1059
strnncat(char * dest,const char * src,int max)1060 char *strnncat(char *dest, const char *src, int max)
1061 {
1062 int l = strlen(dest);
1063 if (max > l) strncpy(dest + l, src, max - l - 1);
1064 dest[max - 1] = 0;
1065 return (dest);
1066 }
1067
1068 // Add C strings to a string with explicit length
1069
wjstrcat(char * dest,int max,const char * s0,int l,...)1070 char *wjstrcat(char *dest, int max, const char *s0, int l, ...)
1071 {
1072 va_list args;
1073 char *s, *w;
1074 int ll;
1075
1076 if (!dest)
1077 {
1078 max = l + 1;
1079 va_start(args, l);
1080 while ((s = va_arg(args, char *))) max += strlen(s);
1081 va_end(args);
1082 dest = malloc(max);
1083 if (!dest) return (NULL);
1084 }
1085
1086 va_start(args, l);
1087 w = dest;
1088 s = (char *)s0; ll = l;
1089 while (TRUE)
1090 {
1091 if (ll >= max) ll = max - 1;
1092 memcpy(w, s, ll);
1093 w += ll;
1094 if ((max -= ll) <= 1) break;
1095 s = va_arg(args, char *);
1096 if (!s) break;
1097 ll = strlen(s);
1098 }
1099 va_end(args);
1100 *w = 0;
1101 return (dest);
1102 }
1103
1104 // Add directory to filename
1105
file_in_dir(char * dest,const char * dir,const char * file,int cnt)1106 char *file_in_dir(char *dest, const char *dir, const char *file, int cnt)
1107 {
1108 int dl = strlen(dir);
1109 return wjstrcat(dest, cnt, dir, dl - (dir[dl - !!dl] == DIR_SEP),
1110 DIR_SEP_STR, file, NULL);
1111 }
1112
file_in_homedir(char * dest,const char * file,int cnt)1113 char *file_in_homedir(char *dest, const char *file, int cnt)
1114 {
1115 return (file_in_dir(dest, get_home_directory(), file, cnt));
1116 }
1117
1118 #if GTK_MAJOR_VERSION <= 2
1119
1120 // Set minimum size for a widget
1121
widget_size_req(GtkWidget * widget,GtkRequisition * requisition,gpointer user_data)1122 static void widget_size_req(GtkWidget *widget, GtkRequisition *requisition,
1123 gpointer user_data)
1124 {
1125 int h = (guint32)user_data >> 16, w = (guint32)user_data & 0xFFFF;
1126
1127 if (h && (requisition->height < h)) requisition->height = h;
1128 if (w && (requisition->width < w)) requisition->width = w;
1129 }
1130
1131 /* !!! Warning: this function can't extend box containers in their "natural"
1132 * direction, because GTK+ takes shortcuts with their allocation, abusing
1133 * requisition value. */
widget_set_minsize(GtkWidget * widget,int width,int height)1134 void widget_set_minsize(GtkWidget *widget, int width, int height)
1135 {
1136 guint32 hw;
1137
1138 if ((width <= 0) && (height <= 0)) return;
1139
1140 hw = (height < 0 ? 0 : height & 0xFFFF) << 16 |
1141 (width < 0 ? 0 : width & 0xFFFF);
1142 gtk_signal_connect(GTK_OBJECT(widget), "size_request",
1143 GTK_SIGNAL_FUNC(widget_size_req), (gpointer)hw);
1144 }
1145
1146 /* This function is a workaround for boxes and the like, wrapping a widget in a
1147 * GtkAlignment and setting size on that - or it can be seen as GtkAlignment
1148 * widget with extended functionality - WJ */
widget_align_minsize(GtkWidget * widget,int width,int height)1149 GtkWidget *widget_align_minsize(GtkWidget *widget, int width, int height)
1150 {
1151 GtkWidget *align = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
1152 gtk_widget_show(align);
1153 gtk_container_add(GTK_CONTAINER(align), widget);
1154 widget_set_minsize(align, width, height);
1155 return (align);
1156 }
1157
1158 #endif
1159
1160 // Make widget request no less size than before (in one direction)
1161
1162 #if GTK_MAJOR_VERSION == 3
1163
widget_size_keep(GtkWidget * widget,GtkAllocation * alloc,gpointer user_data)1164 static void widget_size_keep(GtkWidget *widget, GtkAllocation *alloc,
1165 gpointer user_data)
1166 {
1167 gint w, h, w0, h0;
1168
1169 gtk_widget_get_size_request(widget, &w0, &h0);
1170 if (user_data) // Adjust height if set, width if clear
1171 {
1172 gtk_widget_get_preferred_height(widget, &h, NULL);
1173 h -= gtk_widget_get_margin_top(widget) +
1174 gtk_widget_get_margin_bottom(widget);
1175 if (h0 >= h) return;
1176 h0 = h;
1177 }
1178 else
1179 {
1180 gtk_widget_get_preferred_width(widget, &w, NULL);
1181 w -= gtk_widget_get_margin_start(widget) +
1182 gtk_widget_get_margin_end(widget);
1183 if (w0 >= w) return;
1184 w0 = w;
1185 }
1186 gtk_widget_set_size_request(widget, w0, h0);
1187 }
1188
widget_set_keepsize(GtkWidget * widget,int keep_height)1189 void widget_set_keepsize(GtkWidget *widget, int keep_height)
1190 {
1191 g_signal_connect(widget, "size_allocate",
1192 G_CALLBACK(widget_size_keep), (gpointer)keep_height);
1193 }
1194
1195 #else /* #if GTK_MAJOR_VERSION <= 2 */
1196
1197 #define KEEPSIZE_KEY "mtPaint.keepsize"
1198
1199 static GQuark keepsize_key;
1200
1201 /* And if user manages to change theme on the fly... well, more fool him ;-) */
widget_size_keep(GtkWidget * widget,GtkRequisition * requisition,gpointer user_data)1202 static void widget_size_keep(GtkWidget *widget, GtkRequisition *requisition,
1203 gpointer user_data)
1204 {
1205 int l, l0;
1206
1207 l = (int)gtk_object_get_data_by_id(GTK_OBJECT(widget), keepsize_key);
1208 if (user_data) // Adjust height if set, width if clear
1209 {
1210 if ((l0 = requisition->height) < l) requisition->height = l;
1211 }
1212 else if ((l0 = requisition->width) < l) requisition->width = l;
1213
1214 if (l0 > l) gtk_object_set_data_by_id(GTK_OBJECT(widget), keepsize_key,
1215 (gpointer)l0);
1216 }
1217
1218 /* !!! Warning: this function can't extend box containers in their "natural"
1219 * direction, because GTK+ takes shortcuts with their allocation, abusing
1220 * requisition value. */
widget_set_keepsize(GtkWidget * widget,int keep_height)1221 void widget_set_keepsize(GtkWidget *widget, int keep_height)
1222 {
1223 if (!keepsize_key) keepsize_key = g_quark_from_static_string(KEEPSIZE_KEY);
1224 gtk_signal_connect(GTK_OBJECT(widget), "size_request",
1225 GTK_SIGNAL_FUNC(widget_size_keep), (gpointer)keep_height);
1226 }
1227
1228 #endif
1229
1230 // Workaround for GtkCList reordering bug
1231
1232 /* This bug is the favorite pet of GNOME developer Behdad Esfahbod
1233 * See http://bugzilla.gnome.org/show_bug.cgi?id=400249#c2 */
1234
1235 #if GTK_MAJOR_VERSION == 2
1236
clist_drag_fix(GtkWidget * widget,GdkDragContext * drag_context,gpointer user_data)1237 static void clist_drag_fix(GtkWidget *widget, GdkDragContext *drag_context,
1238 gpointer user_data)
1239 {
1240 g_dataset_remove_data(drag_context, "gtk-clist-drag-source");
1241 }
1242
clist_enable_drag(GtkWidget * clist)1243 void clist_enable_drag(GtkWidget *clist)
1244 {
1245 gtk_signal_connect(GTK_OBJECT(clist), "drag_begin",
1246 GTK_SIGNAL_FUNC(clist_drag_fix), NULL);
1247 gtk_signal_connect(GTK_OBJECT(clist), "drag_end",
1248 GTK_SIGNAL_FUNC(clist_drag_fix), NULL);
1249 gtk_clist_set_reorderable(GTK_CLIST(clist), TRUE);
1250 }
1251
1252 #elif GTK_MAJOR_VERSION == 1 /* GTK1 doesn't have this bug */
1253
clist_enable_drag(GtkWidget * clist)1254 void clist_enable_drag(GtkWidget *clist)
1255 {
1256 gtk_clist_set_reorderable(GTK_CLIST(clist), TRUE);
1257 }
1258
1259 #endif
1260
1261 // Most common use of boxes
1262
pack(GtkWidget * box,GtkWidget * widget)1263 GtkWidget *pack(GtkWidget *box, GtkWidget *widget)
1264 {
1265 gtk_box_pack_start(GTK_BOX(box), widget, FALSE, FALSE, 0);
1266 return (widget);
1267 }
1268
xpack(GtkWidget * box,GtkWidget * widget)1269 GtkWidget *xpack(GtkWidget *box, GtkWidget *widget)
1270 {
1271 gtk_box_pack_start(GTK_BOX(box), widget, TRUE, TRUE, 0);
1272 return (widget);
1273 }
1274
pack_end(GtkWidget * box,GtkWidget * widget)1275 GtkWidget *pack_end(GtkWidget *box, GtkWidget *widget)
1276 {
1277 gtk_box_pack_end(GTK_BOX(box), widget, FALSE, FALSE, 0);
1278 return (widget);
1279 }
1280
1281 // Put vbox into container
1282
add_vbox(GtkWidget * cont)1283 GtkWidget *add_vbox(GtkWidget *cont)
1284 {
1285 #if GTK_MAJOR_VERSION == 3
1286 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1287 #else
1288 GtkWidget *box = gtk_vbox_new(FALSE, 0);
1289 #endif
1290 gtk_widget_show(box);
1291 gtk_container_add(GTK_CONTAINER(cont), box);
1292 return (box);
1293 }
1294
1295 // Fix for paned widgets losing focus in GTK+1
1296
1297 #if GTK_MAJOR_VERSION == 1
1298
fix_gdk_events(GdkWindow * window)1299 static void fix_gdk_events(GdkWindow *window)
1300 {
1301 XWindowAttributes attrs;
1302 GdkWindowPrivate *private = (GdkWindowPrivate *)window;
1303
1304 if (!private || private->destroyed) return;
1305 XGetWindowAttributes(GDK_WINDOW_XDISPLAY(window), private->xwindow, &attrs);
1306 XSelectInput(GDK_WINDOW_XDISPLAY(window), private->xwindow,
1307 attrs.your_event_mask & ~OwnerGrabButtonMask);
1308 }
1309
paned_realize(GtkWidget * widget,gpointer user_data)1310 static void paned_realize(GtkWidget *widget, gpointer user_data)
1311 {
1312 fix_gdk_events(widget->window);
1313 fix_gdk_events(GTK_PANED(widget)->handle);
1314 }
1315
paned_mouse_fix(GtkWidget * widget)1316 void paned_mouse_fix(GtkWidget *widget)
1317 {
1318 gtk_signal_connect(GTK_OBJECT(widget), "realize",
1319 GTK_SIGNAL_FUNC(paned_realize), NULL);
1320 }
1321
1322 #endif
1323
1324 // Init-time bugfixes
1325
1326 /* Bugs: GtkViewport size request in GTK+1; GtkHScale breakage in Smooth Theme
1327 * Engine in GTK+1; GtkListItem and GtkCList stealing Esc key in GTK+1; GtkEntry
1328 * and GtkSpinButton stealing Enter key and mishandling keypad Enter key in
1329 * GTK+1; mixing up keys in GTK+2/Windows; opaque focus rectangle in Gtk-Qt theme
1330 * engine (v0.8) in GTK+2/X */
1331
1332 #if GTK_MAJOR_VERSION == 1
1333
1334 /* This is gtk_viewport_size_request() from GTK+ 1.2.10 with stupid bugs fixed */
gtk_viewport_size_request_fixed(GtkWidget * widget,GtkRequisition * requisition)1335 static void gtk_viewport_size_request_fixed(GtkWidget *widget,
1336 GtkRequisition *requisition)
1337 {
1338 GtkBin *bin;
1339 GtkRequisition child_requisition;
1340
1341 g_return_if_fail(widget != NULL);
1342 g_return_if_fail(GTK_IS_VIEWPORT(widget));
1343 g_return_if_fail(requisition != NULL);
1344
1345 bin = GTK_BIN(widget);
1346
1347 requisition->width = requisition->height =
1348 GTK_CONTAINER(widget)->border_width * 2;
1349
1350 if (GTK_VIEWPORT(widget)->shadow_type != GTK_SHADOW_NONE)
1351 {
1352 requisition->width += widget->style->klass->xthickness * 2;
1353 requisition->height += widget->style->klass->ythickness * 2;
1354 }
1355
1356 if (bin->child && GTK_WIDGET_VISIBLE(bin->child))
1357 {
1358 gtk_widget_size_request(bin->child, &child_requisition);
1359 requisition->width += child_requisition.width;
1360 requisition->height += child_requisition.height;
1361 }
1362 }
1363
1364 /* This is for preventing Smooth Engine from ruining horizontal sliders */
1365 static void (*hsizereq)(GtkWidget *widget, GtkRequisition *requisition);
gtk_hscale_size_request_smooth_fixed(GtkWidget * widget,GtkRequisition * requisition)1366 static void gtk_hscale_size_request_smooth_fixed(GtkWidget *widget,
1367 GtkRequisition *requisition)
1368 {
1369 int realf = GTK_WIDGET_FLAGS(widget) & GTK_REALIZED;
1370 GTK_WIDGET_UNSET_FLAGS(widget, GTK_REALIZED);
1371 hsizereq(widget, requisition);
1372 GTK_WIDGET_SET_FLAGS(widget, realf);
1373 }
1374
1375 typedef struct {
1376 GtkThemeEngine engine;
1377
1378 void *library;
1379 void *name;
1380
1381 void (*init) (GtkThemeEngine *);
1382 void (*exit) (void);
1383
1384 guint refcount;
1385 } GtkThemeEnginePrivate;
1386
1387 /* This is for routing Enter keys around GtkEntry's default handler */
1388 static gboolean (*ekeypress)(GtkWidget *widget, GdkEventKey *event);
gtk_entry_key_press_fixed(GtkWidget * widget,GdkEventKey * event)1389 static gboolean gtk_entry_key_press_fixed(GtkWidget *widget, GdkEventKey *event)
1390 {
1391 if (event && ((event->keyval == GDK_Return) ||
1392 (event->keyval == GDK_KP_Enter))) return (FALSE);
1393 return (ekeypress(widget, event));
1394 }
1395
gtk_init_bugfixes()1396 void gtk_init_bugfixes()
1397 {
1398 GtkWidget *hs;
1399 GtkStyle *st;
1400 GtkWidgetClass *wc;
1401 char *engine = "";
1402
1403
1404 ((GtkWidgetClass*)gtk_type_class(GTK_TYPE_VIEWPORT))->size_request =
1405 gtk_viewport_size_request_fixed;
1406
1407 /* Detect if Smooth Engine is active, and fix its bugs */
1408 st = gtk_rc_get_style(hs = gtk_hscale_new(NULL));
1409 if (st && st->engine)
1410 engine = ((GtkThemeEnginePrivate *)(st->engine))->name;
1411 if (!strcmp(engine, "smooth"))
1412 {
1413 wc = gtk_type_class(GTK_TYPE_HSCALE);
1414 hsizereq = wc->size_request;
1415 wc->size_request = gtk_hscale_size_request_smooth_fixed;
1416 }
1417 gtk_object_sink(GTK_OBJECT(hs)); /* Destroy a floating-ref thing */
1418
1419 gtk_binding_entry_remove(gtk_binding_set_by_class(gtk_type_class(
1420 GTK_TYPE_CLIST)), GDK_Escape, 0);
1421 gtk_binding_entry_remove(gtk_binding_set_by_class(gtk_type_class(
1422 GTK_TYPE_LIST_ITEM)), GDK_Escape, 0);
1423
1424 wc = gtk_type_class(GTK_TYPE_ENTRY);
1425 ekeypress = wc->key_press_event;
1426 wc->key_press_event = gtk_entry_key_press_fixed;
1427 }
1428
1429 #elif GTK_MAJOR_VERSION == 2
1430 #if defined GDK_WINDOWING_WIN32
1431
1432 static int win_last_vk;
1433 static guint32 win_last_lp;
1434
1435 /* Event filter to look at WM_KEYDOWN and WM_SYSKEYDOWN */
win_keys_peek(GdkXEvent * xevent,GdkEvent * event,gpointer data)1436 static GdkFilterReturn win_keys_peek(GdkXEvent *xevent, GdkEvent *event, gpointer data)
1437 {
1438 MSG *msg = xevent;
1439
1440 if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN))
1441 {
1442 /* No matter that these fields are longer in Win64 */
1443 win_last_vk = msg->wParam;
1444 win_last_lp = msg->lParam;
1445 }
1446 return (GDK_FILTER_CONTINUE);
1447 }
1448
1449 #elif defined GDK_WINDOWING_X11
1450
1451 /* Gtk-Qt's author was deluded when he decided he knows how to draw focus;
1452 * doing nothing is *FAR* preferable to opaque box over a widget - WJ */
fake_draw_focus()1453 static void fake_draw_focus()
1454 {
1455 return;
1456 }
1457
1458 #endif
1459
do_shutup(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer user_data)1460 static void do_shutup(const gchar *log_domain, GLogLevelFlags log_level,
1461 const gchar *message, gpointer user_data)
1462 {
1463 char *s = "Invalid UTF-8"; // The words to never utter
1464 if (strncmp(message, s, strlen(s))) g_log_default_handler(log_domain,
1465 log_level, message, user_data);
1466 }
1467
gtk_init_bugfixes()1468 void gtk_init_bugfixes()
1469 {
1470 #if defined GDK_WINDOWING_WIN32
1471 gdk_window_add_filter(NULL, (GdkFilterFunc)win_keys_peek, NULL);
1472 #elif defined GDK_WINDOWING_X11
1473 GtkWidget *bt;
1474 GtkStyleClass *sc;
1475 GType qtt;
1476
1477
1478 /* Detect if Gtk-Qt engine is active, and fix its bugs */
1479 bt = gtk_button_new();
1480 qtt = g_type_from_name("QtEngineStyle");
1481 if (qtt)
1482 {
1483 sc = g_type_class_ref(qtt); /* Have to ref to get it to init */
1484 sc->draw_focus = fake_draw_focus;
1485 }
1486 gtk_object_sink(GTK_OBJECT(bt)); /* Destroy a floating-ref thing */
1487 #endif /* X11 */
1488 /* Cut spam from Pango about bad UTF8 */
1489 g_log_set_handler("Pango", G_LOG_LEVEL_WARNING, (GLogFunc)do_shutup, NULL);
1490
1491 #ifndef U_LISTS_GTK1
1492 /* Remove crazier keybindings from GtkTreeView */
1493 {
1494 GtkBindingSet *bs = gtk_binding_set_by_class(g_type_class_ref(
1495 GTK_TYPE_TREE_VIEW));
1496 gtk_binding_entry_remove(bs, GDK_space, 0); // Activate
1497 gtk_binding_entry_remove(bs, GDK_KP_Space, 0);
1498 gtk_binding_entry_remove(bs, GDK_f, GDK_CONTROL_MASK); // Search
1499 gtk_binding_entry_remove(bs, GDK_F, GDK_CONTROL_MASK);
1500 gtk_binding_entry_remove(bs, GDK_p, GDK_CONTROL_MASK); // Up
1501 gtk_binding_entry_remove(bs, GDK_n, GDK_CONTROL_MASK); // Down
1502 }
1503 #endif
1504 }
1505
1506 #else /* if GTK_MAJOR_VERSION == 3 */
1507
1508 #if defined GDK_WINDOWING_WIN32
1509 #error "GTK+3/Win32 not supported yet"
1510 #endif
1511
1512 int gtk3version;
1513
gtk_init_bugfixes()1514 void gtk_init_bugfixes()
1515 {
1516 GtkContainerClass *c;
1517 GtkBindingSet *bs;
1518
1519 gtk3version = gtk_get_minor_version();
1520
1521 if (gtk3version < 20)
1522 {
1523 /* Fix counting border width twice */
1524 c = g_type_class_ref(GTK_TYPE_RADIO_BUTTON);
1525 c->_handle_border_width = 0;
1526 c = g_type_class_ref(GTK_TYPE_CHECK_BUTTON);
1527 c->_handle_border_width = 0;
1528 /* !!! The wrong idea is introduced at GTK_CHECK_BUTTON level; descendants of
1529 * GTK_BUTTON should let gtk_container_class_handle_border_width() do its thing,
1530 * not add gtk_container_get_border_width() to size by themselves. */
1531 }
1532
1533 /* Remove crazier keybindings from GtkTreeView */
1534 bs = gtk_binding_set_by_class(g_type_class_ref(GTK_TYPE_TREE_VIEW));
1535 gtk_binding_entry_remove(bs, KEY(space), 0); // Activate
1536 gtk_binding_entry_remove(bs, KEY(KP_Space), 0);
1537 gtk_binding_entry_remove(bs, KEY(f), GDK_CONTROL_MASK); // Search
1538 gtk_binding_entry_remove(bs, KEY(F), GDK_CONTROL_MASK);
1539 gtk_binding_entry_remove(bs, KEY(p), GDK_CONTROL_MASK); // Up
1540 gtk_binding_entry_remove(bs, KEY(n), GDK_CONTROL_MASK); // Down
1541 }
1542
1543 #endif /* GTK+3 */
1544
1545 // Whatever is needed to move mouse pointer
1546
1547 #if GTK_MAJOR_VERSION == 3
1548
move_mouse_relative(int dx,int dy)1549 int move_mouse_relative(int dx, int dy)
1550 {
1551 gint x0, y0;
1552 GdkScreen *screen;
1553 GdkDisplay *dp = gtk_widget_get_display(main_window);
1554 GdkDevice *dev;
1555
1556 dev = gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(dp));
1557
1558 gdk_device_get_position(dev, &screen, &x0, &y0);
1559 gdk_device_warp(dev, screen, x0 + dx, y0 + dy);
1560 return (TRUE);
1561 }
1562
1563 #elif (GTK_MAJOR_VERSION == 1) || defined GDK_WINDOWING_X11 /* Call X */
1564
move_mouse_relative(int dx,int dy)1565 int move_mouse_relative(int dx, int dy)
1566 {
1567 XWarpPointer(GDK_WINDOW_XDISPLAY(main_window->window),
1568 None, None, 0, 0, 0, 0, dx, dy);
1569 return (TRUE);
1570 }
1571
1572 #elif defined GDK_WINDOWING_WIN32 /* Call GDI */
1573
move_mouse_relative(int dx,int dy)1574 int move_mouse_relative(int dx, int dy)
1575 {
1576 POINT point;
1577 if (GetCursorPos(&point))
1578 {
1579 SetCursorPos(point.x + dx, point.y + dy);
1580 return (TRUE);
1581 }
1582 else return (FALSE);
1583 }
1584
1585 #elif GTK2VERSION >= 8 /* GTK+ 2.8+ */
1586
move_mouse_relative(int dx,int dy)1587 int move_mouse_relative(int dx, int dy)
1588 {
1589 gint x0, y0;
1590 GdkScreen *screen;
1591 GdkDisplay *display = gtk_widget_get_display(main_window);
1592
1593 gdk_display_get_pointer(display, &screen, &x0, &y0, NULL);
1594 gdk_display_warp_pointer(display, screen, x0 + dx, y0 + dy);
1595 return (TRUE);
1596 }
1597
1598 #else /* Always fail */
1599
move_mouse_relative(int dx,int dy)1600 int move_mouse_relative(int dx, int dy)
1601 {
1602 return (FALSE);
1603 }
1604
1605 #endif
1606
1607 // Whatever is needed to map keyval to key
1608
1609 #if GTK_MAJOR_VERSION == 1 /* Call X */
1610
real_key(GdkEventKey * event)1611 guint real_key(GdkEventKey *event)
1612 {
1613 return (XKeysymToKeycode(GDK_WINDOW_XDISPLAY(main_window->window),
1614 event->keyval));
1615 }
1616
low_key(GdkEventKey * event)1617 guint low_key(GdkEventKey *event)
1618 {
1619 return (gdk_keyval_to_lower(event->keyval));
1620 }
1621
keyval_key(guint keyval)1622 guint keyval_key(guint keyval)
1623 {
1624 return (XKeysymToKeycode(GDK_WINDOW_XDISPLAY(main_window->window),
1625 keyval));
1626 }
1627
1628 #else /* Use GDK */
1629
real_key(GdkEventKey * event)1630 guint real_key(GdkEventKey *event)
1631 {
1632 return (event->hardware_keycode);
1633 }
1634
1635 #ifdef GDK_WINDOWING_WIN32
1636
1637 /* Keypad translation helpers */
1638 static unsigned char keypad_vk[] = {
1639 VK_CLEAR, VK_PRIOR, VK_NEXT, VK_END, VK_HOME,
1640 VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN, VK_INSERT, VK_DELETE,
1641 VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4,
1642 VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9,
1643 VK_DECIMAL, 0
1644 };
1645 static unsigned short keypad_wgtk[] = {
1646 GDK_Clear, GDK_Page_Up, GDK_Page_Down, GDK_End, GDK_Home,
1647 GDK_Left, GDK_Up, GDK_Right, GDK_Down, GDK_Insert, GDK_Delete,
1648 GDK_0, GDK_1, GDK_2, GDK_3, GDK_4,
1649 GDK_5, GDK_6, GDK_7, GDK_8, GDK_9,
1650 GDK_period, 0
1651 };
1652 static unsigned short keypad_xgtk[] = {
1653 GDK_KP_Begin, GDK_KP_Page_Up, GDK_KP_Page_Down, GDK_KP_End, GDK_KP_Home,
1654 GDK_KP_Left, GDK_KP_Up, GDK_KP_Right, GDK_KP_Down, GDK_KP_Insert, GDK_KP_Delete,
1655 GDK_KP_0, GDK_KP_1, GDK_KP_2, GDK_KP_3, GDK_KP_4,
1656 GDK_KP_5, GDK_KP_6, GDK_KP_7, GDK_KP_8, GDK_KP_9,
1657 GDK_KP_Decimal, 0
1658 };
1659
low_key(GdkEventKey * event)1660 guint low_key(GdkEventKey *event)
1661 {
1662 /* Augment braindead GDK translation by recognizing keypad keys */
1663 if (win_last_vk == event->hardware_keycode) /* Paranoia */
1664 {
1665 if (win_last_lp & 0x01000000) /* Extended key */
1666 {
1667 if (event->keyval == GDK_Return) return (GDK_KP_Enter);
1668 }
1669 else /* Normal key */
1670 {
1671 unsigned char *cp = strchr(keypad_vk, event->hardware_keycode);
1672 if (cp && (event->keyval == keypad_wgtk[cp - keypad_vk]))
1673 return (keypad_xgtk[cp - keypad_vk]);
1674 }
1675 }
1676 return (gdk_keyval_to_lower(event->keyval));
1677 }
1678
1679 #else /* X11/Quartz/whatever */
1680
low_key(GdkEventKey * event)1681 guint low_key(GdkEventKey *event)
1682 {
1683 return (gdk_keyval_to_lower(event->keyval));
1684 }
1685
1686 #endif
1687
keyval_key(guint keyval)1688 guint keyval_key(guint keyval)
1689 {
1690 GdkDisplay *display = gtk_widget_get_display(main_window);
1691 GdkKeymap *keymap = gdk_keymap_get_for_display(display);
1692 GdkKeymapKey *key;
1693 gint nkeys;
1694
1695 if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &key, &nkeys))
1696 {
1697 #ifdef GDK_WINDOWING_WIN32
1698 /* Keypad keys need specialcasing on Windows */
1699 for (nkeys = 0; keypad_xgtk[nkeys] &&
1700 (keyval != keypad_xgtk[nkeys]); nkeys++);
1701 return (keypad_vk[nkeys]);
1702 #endif
1703 return (0);
1704 }
1705 if (!nkeys) return (0);
1706 keyval = key[0].keycode;
1707 g_free(key);
1708 return (keyval);
1709 }
1710
1711 #endif
1712
1713 // Interpreting arrow keys
1714
arrow_key_(unsigned key,unsigned state,int * dx,int * dy,int mult)1715 int arrow_key_(unsigned key, unsigned state, int *dx, int *dy, int mult)
1716 {
1717 if ((state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) !=
1718 GDK_SHIFT_MASK) mult = 1;
1719 *dx = *dy = 0;
1720 switch (key)
1721 {
1722 case KEY(KP_Left): case KEY(Left):
1723 *dx = -mult; break;
1724 case KEY(KP_Right): case KEY(Right):
1725 *dx = mult; break;
1726 case KEY(KP_Up): case KEY(Up):
1727 *dy = -mult; break;
1728 case KEY(KP_Down): case KEY(Down):
1729 *dy = mult; break;
1730 }
1731 return (*dx || *dy);
1732 }
1733
1734 // Create pixmap cursor
1735
1736 #if GTK_MAJOR_VERSION == 3
1737
1738 /* Assemble two XBMs into one BW ARGB32 surface */
xbms_to_surface(unsigned char * image,unsigned char * mask,int w,int h)1739 static cairo_surface_t *xbms_to_surface(unsigned char *image, unsigned char *mask,
1740 int w, int h)
1741 {
1742 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
1743 unsigned char *dst0 = cairo_image_surface_get_data(s);
1744 int st = cairo_image_surface_get_stride(s);
1745 unsigned int b = 0;
1746
1747 cairo_surface_flush(s);
1748 while (h-- > 0)
1749 {
1750 guint32 *dest = (void *)dst0;
1751 int n;
1752
1753 for (n = w; n-- > 0; b++)
1754 *dest++ = ((image[b >> 3] >> (b & 7)) & 1) * 0x00FFFFFFU +
1755 ((mask[b >> 3] >> (b & 7)) & 1) * 0xFF000000U;
1756 dst0 += st; b += (~b + 1) & 7;
1757 }
1758 cairo_surface_mark_dirty(s);
1759 return (s);
1760 }
1761
make_cursor(char * icon,char * mask,int w,int h,int tip_x,int tip_y)1762 GdkCursor *make_cursor(char *icon, char *mask, int w, int h, int tip_x, int tip_y)
1763 {
1764 cairo_surface_t *s = xbms_to_surface(icon, mask, w, h);
1765 GdkCursor *cursor = gdk_cursor_new_from_surface(
1766 gtk_widget_get_display(main_window), s, tip_x, tip_y);
1767 cairo_surface_fdestroy(s);
1768 return (cursor);
1769 }
1770
1771 #else /* GTK_MAJOR_VERSION <= 2 */
1772
make_cursor(char * icon,char * mask,int w,int h,int tip_x,int tip_y)1773 GdkCursor *make_cursor(char *icon, char *mask, int w, int h, int tip_x, int tip_y)
1774 {
1775 static GdkColor cfg = { -1, -1, -1, -1 }, cbg = { 0, 0, 0, 0 };
1776 GdkPixmap *icn, *msk;
1777 GdkCursor *cursor;
1778
1779 icn = gdk_bitmap_create_from_data(NULL, icon, w, h);
1780 msk = gdk_bitmap_create_from_data(NULL, mask, w, h);
1781 cursor = gdk_cursor_new_from_pixmap(icn, msk, &cfg, &cbg, tip_x, tip_y);
1782 gdk_pixmap_unref(icn);
1783 gdk_pixmap_unref(msk);
1784 return (cursor);
1785 }
1786
1787 #endif
1788
1789 // Menu-like combo box
1790
1791 #if GTK_MAJOR_VERSION == 3
1792
1793 /* Making GtkComboBox behave is quite nontrivial, here */
1794
wj_combo_click(GtkWidget * widget,GdkEventButton * event,gpointer user_data)1795 static gboolean wj_combo_click(GtkWidget *widget, GdkEventButton *event,
1796 gpointer user_data)
1797 {
1798 if (user_data)
1799 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(user_data), TRUE);
1800 return (TRUE);
1801 }
1802
1803 /* void handler(GtkWidget *combo, gpointer user_data); */
wj_combo_box(char ** names,int cnt,int u,int idx,void ** r,GCallback handler)1804 GtkWidget *wj_combo_box(char **names, int cnt, int u, int idx, void **r,
1805 GCallback handler)
1806 {
1807 GtkWidget *cbox, *entry, *button;
1808 int i;
1809
1810 if (idx >= cnt) idx = 0;
1811 cbox = gtk_combo_box_text_new_with_entry();
1812 /* Find the button */
1813 button = combobox_button(cbox);
1814
1815 /* Make the entry a dumb display */
1816 entry = gtk_bin_get_child(GTK_BIN(cbox));
1817 g_object_set(entry, "editable", FALSE, NULL);
1818 gtk_widget_set_can_focus(entry, FALSE);
1819 /* Make click on entry do popup too */
1820 g_signal_connect(entry, "button_press_event", G_CALLBACK(wj_combo_click), NULL);
1821 g_signal_connect(entry, "button_release_event", G_CALLBACK(wj_combo_click), button);
1822
1823 for (i = 0; i < cnt; i++)
1824 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cbox), __(names[i]));
1825 gtk_combo_box_set_active(GTK_COMBO_BOX(cbox), idx);
1826 if (handler) g_signal_connect(G_OBJECT(cbox), "changed",
1827 handler, r);
1828
1829 return (cbox);
1830 }
1831
wj_combo_box_get_history(GtkWidget * combobox)1832 int wj_combo_box_get_history(GtkWidget *combobox)
1833 {
1834 return (gtk_combo_box_get_active(GTK_COMBO_BOX(combobox)));
1835 }
1836
1837 /* Use GtkComboBox when available */
1838 #elif GTK2VERSION >= 4 /* GTK+ 2.4+ */
1839
1840 /* Tweak style settings for combo box */
wj_combo_restyle(GtkWidget * cbox)1841 static void wj_combo_restyle(GtkWidget *cbox)
1842 {
1843 static int done;
1844 if (!done)
1845 {
1846 gtk_rc_parse_string("style \"mtPaint_cblist\" {\n"
1847 "GtkComboBox::appears-as-list = 1\n}\n");
1848 gtk_rc_parse_string("widget \"*.mtPaint_cbox\" "
1849 "style \"mtPaint_cblist\"\n");
1850 done = TRUE;
1851 }
1852 gtk_widget_set_name(cbox, "mtPaint_cbox");
1853 }
1854
1855 /* void handler(GtkWidget *combo, gpointer user_data); */
wj_combo_box(char ** names,int cnt,int u,int idx,void ** r,GtkSignalFunc handler)1856 GtkWidget *wj_combo_box(char **names, int cnt, int u, int idx, void **r,
1857 GtkSignalFunc handler)
1858 {
1859 GtkWidget *cbox;
1860 GtkComboBox *combo;
1861 int i;
1862
1863
1864 if (idx >= cnt) idx = 0;
1865 combo = GTK_COMBO_BOX(cbox = gtk_combo_box_new_text());
1866 wj_combo_restyle(cbox);
1867 for (i = 0; i < cnt; i++) gtk_combo_box_append_text(combo, __(names[i]));
1868 gtk_combo_box_set_active(combo, idx);
1869 if (handler) gtk_signal_connect(GTK_OBJECT(cbox), "changed",
1870 handler, r);
1871
1872 return (cbox);
1873 }
1874
wj_combo_box_get_history(GtkWidget * combobox)1875 int wj_combo_box_get_history(GtkWidget *combobox)
1876 {
1877 return (gtk_combo_box_get_active(GTK_COMBO_BOX(combobox)));
1878 }
1879
1880 #else /* Use GtkCombo before GTK+ 2.4.0 */
1881
1882 /* !!! In GTK+2, this handler is called twice for each change; in GTK+1,
1883 * once if using cursor keys, twice if selecting from list */
wj_combo(GtkWidget * entry,gpointer handler)1884 static void wj_combo(GtkWidget *entry, gpointer handler)
1885 {
1886 GtkWidget *combo = entry->parent;
1887 gpointer user_data;
1888
1889 /* GTK+1 updates the entry constantly - wait it out */
1890 #if GTK_MAJOR_VERSION == 1
1891 if (GTK_WIDGET_VISIBLE(GTK_COMBO(combo)->popwin)) return;
1892 #endif
1893 user_data = gtk_object_get_user_data(GTK_OBJECT(entry));
1894 ((void (*)(GtkWidget *, gpointer))handler)(combo, user_data);
1895 }
1896
1897 #if GTK_MAJOR_VERSION == 1
1898 /* Notify the main handler that meaningless updates are finished */
wj_combo_restart(GtkWidget * widget,GtkCombo * combo)1899 static void wj_combo_restart(GtkWidget *widget, GtkCombo *combo)
1900 {
1901 gtk_signal_emit_by_name(GTK_OBJECT(combo->entry), "changed");
1902 }
1903 #endif
1904
1905 #if GTK_MAJOR_VERSION == 2
1906 /* Tweak style settings for combo entry */
wj_combo_restyle(GtkWidget * entry)1907 static void wj_combo_restyle(GtkWidget *entry)
1908 {
1909 static int done;
1910 if (!done)
1911 {
1912 gtk_rc_parse_string("style \"mtPaint_extfocus\" {\n"
1913 "GtkWidget::interior-focus = 0\n}\n");
1914 gtk_rc_parse_string("widget \"*.mtPaint_cbentry\" "
1915 "style \"mtPaint_extfocus\"\n");
1916 done = TRUE;
1917 }
1918 gtk_widget_set_name(entry, "mtPaint_cbentry");
1919 }
1920
1921 /* Prevent cursor from appearing */
wj_combo_kill_cursor(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)1922 static gboolean wj_combo_kill_cursor(GtkWidget *widget, GdkEventExpose *event,
1923 gpointer user_data)
1924 {
1925 /* !!! Private field - future binary compatibility isn't guaranteed */
1926 GTK_ENTRY(widget)->cursor_visible = FALSE;
1927 return (FALSE);
1928 }
1929 #endif
1930
1931 /* void handler(GtkWidget *combo, gpointer user_data); */
wj_combo_box(char ** names,int cnt,int u,int idx,void ** r,GtkSignalFunc handler)1932 GtkWidget *wj_combo_box(char **names, int cnt, int u, int idx, void **r,
1933 GtkSignalFunc handler)
1934 {
1935 GtkWidget *cbox;
1936 GtkCombo *combo;
1937 GtkEntry *entry;
1938 GList *list = NULL;
1939 int i;
1940
1941
1942 if (idx >= cnt) idx = 0;
1943 combo = GTK_COMBO(cbox = gtk_combo_new());
1944 #if GTK_MAJOR_VERSION == 2
1945 wj_combo_restyle(combo->entry);
1946 gtk_signal_connect(GTK_OBJECT(combo->entry), "expose_event",
1947 GTK_SIGNAL_FUNC(wj_combo_kill_cursor), NULL);
1948 #endif
1949 gtk_combo_set_value_in_list(combo, TRUE, FALSE);
1950 for (i = 0; i < cnt; i++) list = g_list_append(list, __(names[i]));
1951 gtk_combo_set_popdown_strings(combo, list);
1952 g_list_free(list);
1953 gtk_widget_show_all(cbox);
1954 entry = GTK_ENTRY(combo->entry);
1955 gtk_entry_set_editable(entry, FALSE);
1956 gtk_entry_set_text(entry, names[idx]);
1957 if (!handler) return (cbox);
1958
1959 /* Install signal handler */
1960 gtk_object_set_user_data(GTK_OBJECT(combo->entry), r);
1961 gtk_signal_connect(GTK_OBJECT(combo->entry), "changed",
1962 GTK_SIGNAL_FUNC(wj_combo), (gpointer)handler);
1963 #if GTK_MAJOR_VERSION == 1
1964 gtk_signal_connect(GTK_OBJECT(combo->popwin), "hide",
1965 GTK_SIGNAL_FUNC(wj_combo_restart), combo);
1966 #endif
1967
1968 return (cbox);
1969 }
1970
wj_combo_box_get_history(GtkWidget * combobox)1971 int wj_combo_box_get_history(GtkWidget *combobox)
1972 {
1973 GtkList *list = GTK_LIST(GTK_COMBO(combobox)->list);
1974
1975 if (!list->selection || !list->selection->data) return (-1);
1976 return(gtk_list_child_position(list, GTK_WIDGET(list->selection->data)));
1977 }
1978
1979 #endif
1980
1981 #if GTK_MAJOR_VERSION == 3
1982
1983 // Bin widget with customizable size handling
1984
1985 /* The only way to intercept size requests in GTK+3 is a wrapper widget. This is
1986 * such a widget, with installable handlers */
1987
1988 #define WJSIZEBIN(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, wjsizebin_get_type(), wjsizebin)
1989 #define IS_WJSIZEBIN(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, wjsizebin_get_type())
1990
1991 typedef void (*size_alloc_f)(GtkWidget *widget, GtkAllocation *allocation,
1992 gpointer user_data);
1993 typedef void (*get_size_f)(GtkWidget *widget, gint vert, gint *min, gint *nat,
1994 gint for_width, gpointer user_data);
1995
1996 typedef struct
1997 {
1998 GtkBin bin; // Parent class
1999 size_alloc_f size_alloc; // Allocate child
2000 get_size_f get_size; // Modify requested size
2001 gpointer udata;
2002 } wjsizebin;
2003
2004 typedef struct
2005 {
2006 GtkBinClass parent_class;
2007 } wjsizebinClass;
2008
G_DEFINE_TYPE(wjsizebin,wjsizebin,GTK_TYPE_BIN)2009 G_DEFINE_TYPE(wjsizebin, wjsizebin, GTK_TYPE_BIN)
2010
2011 static void wjsizebin_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
2012 {
2013 wjsizebin *sbin = WJSIZEBIN(widget);
2014 GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
2015
2016 gtk_widget_set_allocation(widget, allocation);
2017 if (!child || !gtk_widget_get_visible(child)) return;
2018 /* Call handler if installed, do default thing if not */
2019 if (sbin->size_alloc) sbin->size_alloc(child, allocation, sbin->udata);
2020 else gtk_widget_size_allocate(child, allocation);
2021 }
2022
wjsizebin_get_size(GtkWidget * widget,gint vert,gint * min,gint * nat,gint for_width)2023 static void wjsizebin_get_size(GtkWidget *widget, gint vert, gint *min, gint *nat,
2024 gint for_width)
2025 {
2026 wjsizebin *sbin = WJSIZEBIN(widget);
2027 GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
2028
2029 /* Preset the size */
2030 *min = *nat = 0; // Default
2031 if (!child || !gtk_widget_get_visible(child)); // Invisible doesn't matter
2032 else if (for_width >= 0)
2033 gtk_widget_get_preferred_height_for_width(child, for_width, min, nat);
2034 else (vert ? gtk_widget_get_preferred_height :
2035 gtk_widget_get_preferred_width)(child, min, nat);
2036 /* Let handler modify it */
2037 if (sbin->get_size)
2038 sbin->get_size(child, vert, min, nat, for_width, sbin->udata);
2039 }
2040
wjsizebin_get_preferred_width(GtkWidget * widget,gint * min,gint * nat)2041 static void wjsizebin_get_preferred_width(GtkWidget *widget, gint *min, gint *nat)
2042 {
2043 wjsizebin_get_size(widget, FALSE, min, nat, -1);
2044 }
2045
wjsizebin_get_preferred_height(GtkWidget * widget,gint * min,gint * nat)2046 static void wjsizebin_get_preferred_height(GtkWidget *widget, gint *min, gint *nat)
2047 {
2048 wjsizebin_get_size(widget, TRUE, min, nat, -1);
2049 }
2050
wjsizebin_get_preferred_width_for_height(GtkWidget * widget,gint h,gint * min,gint * nat)2051 static void wjsizebin_get_preferred_width_for_height(GtkWidget *widget, gint h,
2052 gint *min, gint *nat)
2053 {
2054 wjsizebin_get_size(widget, FALSE, min, nat, -1);
2055 }
2056
2057 /* Specialcase only height-for-width, same as GtkFrame does */
wjsizebin_get_preferred_height_for_width(GtkWidget * widget,gint w,gint * min,gint * nat)2058 static void wjsizebin_get_preferred_height_for_width(GtkWidget *widget, gint w,
2059 gint *min, gint *nat)
2060 {
2061 wjsizebin_get_size(widget, TRUE, min, nat, w);
2062 }
2063
wjsizebin_class_init(wjsizebinClass * class)2064 static void wjsizebin_class_init(wjsizebinClass *class)
2065 {
2066 GtkWidgetClass *wclass = GTK_WIDGET_CLASS(class);
2067
2068 wclass->size_allocate = wjsizebin_size_allocate;
2069 wclass->get_preferred_width = wjsizebin_get_preferred_width;
2070 wclass->get_preferred_height = wjsizebin_get_preferred_height;
2071 wclass->get_preferred_width_for_height = wjsizebin_get_preferred_width_for_height;
2072 wclass->get_preferred_height_for_width = wjsizebin_get_preferred_height_for_width;
2073 /* !!! Leave my bin alone */
2074 wclass->style_updated = NULL;
2075 }
2076
wjsizebin_init(wjsizebin * sbin)2077 static void wjsizebin_init(wjsizebin *sbin)
2078 {
2079 gtk_widget_set_has_window(GTK_WIDGET(sbin), FALSE);
2080 }
2081
wjsizebin_new(GCallback get_size,GCallback size_alloc,gpointer user_data)2082 GtkWidget *wjsizebin_new(GCallback get_size, GCallback size_alloc, gpointer user_data)
2083 {
2084 GtkWidget *w = g_object_new(wjsizebin_get_type(), NULL);
2085 wjsizebin *sbin = WJSIZEBIN(w);
2086 sbin->get_size = (get_size_f)get_size;
2087 sbin->size_alloc = (size_alloc_f)size_alloc;
2088 sbin->udata = user_data;
2089 return (w);
2090 }
2091
2092 #else /* #if GTK_MAJOR_VERSION <= 2 */
2093
2094 // Box widget with customizable size handling
2095
2096 /* There exist no way to override builtin handlers for GTK_RUN_FIRST signals,
2097 * such as size-request and size-allocate; so instead of building multiple
2098 * custom widget classes with different resize handling, it's better to build
2099 * one with no builtin sizing at all - WJ */
2100
wj_size_box()2101 GtkWidget *wj_size_box()
2102 {
2103 static GtkType size_box_type;
2104 GtkWidget *widget;
2105
2106 if (!size_box_type)
2107 {
2108 static const GtkTypeInfo info = {
2109 "WJSizeBox", sizeof(GtkBox),
2110 sizeof(GtkBoxClass), NULL /* class init */,
2111 NULL /* instance init */, NULL, NULL, NULL };
2112 GtkWidgetClass *wc;
2113 size_box_type = gtk_type_unique(GTK_TYPE_BOX, &info);
2114 wc = gtk_type_class(size_box_type);
2115 wc->size_request = NULL;
2116 wc->size_allocate = NULL;
2117 }
2118 widget = gtk_widget_new(size_box_type, NULL);
2119 GTK_WIDGET_SET_FLAGS(widget, GTK_NO_WINDOW);
2120 #if GTK_MAJOR_VERSION == 2
2121 gtk_widget_set_redraw_on_allocate(widget, FALSE);
2122 #endif
2123 gtk_widget_show(widget);
2124 return (widget);
2125 }
2126
2127 // Disable visual updates while tweaking container's contents
2128
2129 /* This is an evil hack, and isn't guaranteed to work in future GTK+ versions;
2130 * still, not providing such a function is a design mistake in GTK+, and it'll
2131 * be easier to update this code if it becomes broken sometime in dim future,
2132 * than deal with premature updates right here and now - WJ */
2133
2134 typedef struct {
2135 int flags, pf, mode;
2136 } lock_state;
2137
toggle_updates(GtkWidget * widget,gpointer unlock)2138 gpointer toggle_updates(GtkWidget *widget, gpointer unlock)
2139 {
2140 lock_state *state;
2141 GtkContainer *cont = GTK_CONTAINER(widget);
2142
2143 if (!unlock) /* Locking... */
2144 {
2145 state = calloc(1, sizeof(lock_state));
2146 state->mode = cont->resize_mode;
2147 cont->resize_mode = GTK_RESIZE_IMMEDIATE;
2148 state->flags = GTK_WIDGET_FLAGS(widget);
2149 #if GTK_MAJOR_VERSION == 1
2150 GTK_WIDGET_UNSET_FLAGS(widget, GTK_VISIBLE);
2151 state->pf = GTK_WIDGET_IS_OFFSCREEN(widget);
2152 GTK_PRIVATE_SET_FLAG(widget, GTK_IS_OFFSCREEN);
2153 #else /* if GTK_MAJOR_VERSION == 2 */
2154 GTK_WIDGET_UNSET_FLAGS(widget, GTK_VISIBLE | GTK_MAPPED);
2155 #endif
2156 }
2157 else /* Unlocking... */
2158 {
2159 state = unlock;
2160 cont->resize_mode = state->mode;
2161 #if GTK_MAJOR_VERSION == 1
2162 GTK_WIDGET_SET_FLAGS(widget, state->flags & GTK_VISIBLE);
2163 if (!state->pf) GTK_PRIVATE_UNSET_FLAG(widget, GTK_IS_OFFSCREEN);
2164 #else /* if GTK_MAJOR_VERSION == 2 */
2165 GTK_WIDGET_SET_FLAGS(widget, state->flags & (GTK_VISIBLE | GTK_MAPPED));
2166 #endif
2167 free(state);
2168 state = NULL;
2169 }
2170 return (state);
2171 }
2172
2173 #endif
2174
2175 // Maximized & iconified states
2176
2177 #if GTK_MAJOR_VERSION == 1
2178
2179 static Atom netwm[4];
2180 static int netwm_set;
2181
init_netwm(GdkWindow * w)2182 static int init_netwm(GdkWindow *w)
2183 {
2184 static char *nm[4] = {
2185 "_NET_WM_STATE",
2186 "_NET_WM_STATE_MAXIMIZED_VERT",
2187 "_NET_WM_STATE_MAXIMIZED_HORZ",
2188 "_NET_WM_STATE_HIDDEN" };
2189 return (XInternAtoms(GDK_WINDOW_XDISPLAY(w), nm, 4, FALSE, netwm));
2190 }
2191
is_maximized(GtkWidget * window)2192 int is_maximized(GtkWidget *window)
2193 {
2194 Atom type, *atoms;
2195 int format, vh = 0;
2196 unsigned long i, nitems, after;
2197 unsigned char *data;
2198 GdkWindow *w = window->window;
2199
2200 if (!netwm_set) netwm_set = init_netwm(w);
2201
2202 gdk_error_trap_push();
2203 XGetWindowProperty(GDK_WINDOW_XDISPLAY(w), GDK_WINDOW_XWINDOW(w),
2204 netwm[0], 0, G_MAXLONG, False, XA_ATOM,
2205 &type, &format, &nitems, &after, &data);
2206 gdk_error_trap_pop();
2207
2208 atoms = (void *)data;
2209 for (i = 0; i < nitems; i++)
2210 {
2211 if (atoms[i] == netwm[1]) vh |= 1;
2212 else if (atoms[i] == netwm[2]) vh |= 2;
2213 }
2214 XFree(atoms);
2215 return (vh == 3);
2216 }
2217
do_wm_state(GdkWindow * w,int add,int i1,int i2)2218 static void do_wm_state(GdkWindow *w, int add, int i1, int i2)
2219 {
2220 XEvent xev;
2221
2222 if (!netwm_set) netwm_set = init_netwm(w);
2223
2224 xev.xclient.type = ClientMessage;
2225 xev.xclient.serial = 0;
2226 xev.xclient.send_event = True;
2227 xev.xclient.window = GDK_WINDOW_XWINDOW(w);
2228 xev.xclient.message_type = netwm[0];
2229 xev.xclient.format = 32;
2230 xev.xclient.data.l[0] = add ? 1 : 0; /* _NET_WM_STATE_ADD : _REMOVE */
2231 xev.xclient.data.l[1] = netwm[i1];
2232 xev.xclient.data.l[2] = i2 ? netwm[i2] : 0;
2233 xev.xclient.data.l[3] = 0;
2234 xev.xclient.data.l[4] = 0;
2235
2236 XSendEvent(GDK_WINDOW_XDISPLAY(w), DefaultRootWindow(GDK_WINDOW_XDISPLAY(w)),
2237 False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
2238 }
2239
set_maximized(GtkWidget * window)2240 void set_maximized(GtkWidget *window)
2241 {
2242 do_wm_state(window->window, TRUE, 1, 2);
2243 }
2244
set_iconify(GtkWidget * window,int state)2245 void set_iconify(GtkWidget *window, int state)
2246 {
2247 GdkWindow *w = window->window;
2248
2249 if (!GTK_WIDGET_MAPPED(window)) return;
2250 if (state) XIconifyWindow(GDK_WINDOW_XDISPLAY(w), GDK_WINDOW_XWINDOW(w),
2251 DefaultScreen(GDK_WINDOW_XDISPLAY(w)));
2252 else
2253 {
2254 gdk_window_show(w);
2255 do_wm_state(w, FALSE, 3, 0);
2256 }
2257 }
2258
2259 #else /* if GTK_MAJOR_VERSION >= 2 */
2260
set_iconify(GtkWidget * window,int state)2261 void set_iconify(GtkWidget *window, int state)
2262 {
2263 (state ? gdk_window_iconify : gdk_window_deiconify)(gtk_widget_get_window(window));
2264 }
2265
2266 #endif
2267
2268 // Drawable to RGB
2269
2270 /* This code exists to read back both windows and pixmaps. In GTK+1 pixmap
2271 * handling capabilities are next to nonexistent, so a GdkWindow must always
2272 * be passed in, to serve as source of everything but actual pixels.
2273 * The only exception is when the pixmap passed in is definitely a bitmap.
2274 * Parsing GdkImage pixels loosely follows the example of convert_real_slow()
2275 * in GTK+2 (gdk/gdkpixbuf-drawable.c) - WJ
2276 */
2277
2278 #if GTK_MAJOR_VERSION == 1
2279
wj_get_rgb_image(GdkWindow * window,GdkPixmap * pixmap,unsigned char * buf,int x,int y,int width,int height)2280 unsigned char *wj_get_rgb_image(GdkWindow *window, GdkPixmap *pixmap,
2281 unsigned char *buf, int x, int y, int width, int height)
2282 {
2283 GdkImage *img;
2284 GdkColormap *cmap;
2285 GdkVisual *vis, fake_vis;
2286 GdkColor bw[2], *cols = NULL;
2287 unsigned char *dest, *wbuf = NULL;
2288 guint32 rmask, gmask, bmask, pix;
2289 int mode, rshift, gshift, bshift;
2290 int i, j;
2291
2292
2293 if (!window) /* No window - we got us a bitmap */
2294 {
2295 vis = &fake_vis;
2296 vis->type = GDK_VISUAL_STATIC_GRAY;
2297 vis->depth = 1;
2298 cmap = NULL;
2299 }
2300 else if (window == (GdkWindow *)&gdk_root_parent) /* Not a proper window */
2301 {
2302 vis = gdk_visual_get_system();
2303 cmap = gdk_colormap_get_system();
2304 }
2305 else
2306 {
2307 vis = gdk_window_get_visual(window);
2308 cmap = gdk_window_get_colormap(window);
2309 }
2310
2311 if (!vis) return (NULL);
2312 mode = vis->type;
2313
2314 if (cmap) cols = cmap->colors;
2315 else if ((mode != GDK_VISUAL_TRUE_COLOR) && (vis->depth != 1))
2316 return (NULL); /* Can't handle other types w/o colormap */
2317
2318 if (!buf) buf = wbuf = malloc(width * height * 3);
2319 if (!buf) return (NULL);
2320
2321 img = gdk_image_get(pixmap ? pixmap : window, x, y, width, height);
2322 if (!img)
2323 {
2324 free(wbuf);
2325 return (NULL);
2326 }
2327
2328 rmask = vis->red_mask;
2329 gmask = vis->green_mask;
2330 bmask = vis->blue_mask;
2331 rshift = vis->red_shift;
2332 gshift = vis->green_shift;
2333 bshift = vis->blue_shift;
2334
2335 if (mode == GDK_VISUAL_TRUE_COLOR)
2336 {
2337 /* !!! Unlikely to happen, but it's cheap to be safe */
2338 if (vis->red_prec > 8) rshift += vis->red_prec - 8;
2339 if (vis->green_prec > 8) gshift += vis->green_prec - 8;
2340 if (vis->blue_prec > 8) bshift += vis->blue_prec - 8;
2341 }
2342 else if (!cmap && (vis->depth == 1)) /* Bitmap */
2343 {
2344 /* Make a palette for it */
2345 mode = GDK_VISUAL_PSEUDO_COLOR;
2346 bw[0].red = bw[0].green = bw[0].blue = 0;
2347 bw[1].red = bw[1].green = bw[1].blue = 65535;
2348 cols = bw;
2349 }
2350
2351 dest = buf;
2352 for (i = 0; i < height; i++)
2353 for (j = 0; j < width; j++ , dest += 3)
2354 {
2355 pix = gdk_image_get_pixel(img, j, i);
2356 if (mode == GDK_VISUAL_TRUE_COLOR)
2357 {
2358 dest[0] = (pix & rmask) >> rshift;
2359 dest[1] = (pix & gmask) >> gshift;
2360 dest[2] = (pix & bmask) >> bshift;
2361 }
2362 else if (mode == GDK_VISUAL_DIRECT_COLOR)
2363 {
2364 dest[0] = cols[(pix & rmask) >> rshift].red >> 8;
2365 dest[1] = cols[(pix & gmask) >> gshift].green >> 8;
2366 dest[2] = cols[(pix & bmask) >> bshift].blue >> 8;
2367 }
2368 else /* Paletted */
2369 {
2370 dest[0] = cols[pix].red >> 8;
2371 dest[1] = cols[pix].green >> 8;
2372 dest[2] = cols[pix].blue >> 8;
2373 }
2374 }
2375
2376 /* Now extend the precision to 8 bits where necessary */
2377 if (mode == GDK_VISUAL_TRUE_COLOR)
2378 {
2379 unsigned char xlat[128], *dest;
2380 int i, j, k, l = width * height;
2381
2382 for (i = 0; i < 3; i++)
2383 {
2384 k = !i ? vis->red_prec : i == 1 ? vis->green_prec :
2385 vis->blue_prec;
2386 if (k >= 8) continue;
2387 set_xlate(xlat, k);
2388 dest = buf + i;
2389 for (j = 0; j < l; j++ , dest += 3)
2390 *dest = xlat[*dest];
2391 }
2392 }
2393
2394 gdk_image_destroy(img);
2395 return (buf);
2396 }
2397
2398 #elif GTK_MAJOR_VERSION == 2
2399
wj_get_rgb_image(GdkWindow * window,GdkPixmap * pixmap,unsigned char * buf,int x,int y,int width,int height)2400 unsigned char *wj_get_rgb_image(GdkWindow *window, GdkPixmap *pixmap,
2401 unsigned char *buf, int x, int y, int width, int height)
2402 {
2403 GdkColormap *cmap = NULL;
2404 GdkPixbuf *pix, *res;
2405 unsigned char *wbuf = NULL;
2406
2407 if (!buf) buf = wbuf = malloc(width * height * 3);
2408 if (!buf) return (NULL);
2409
2410 if (pixmap && window)
2411 {
2412 cmap = gdk_drawable_get_colormap(pixmap);
2413 if (!cmap) cmap = gdk_drawable_get_colormap(window);
2414 }
2415 pix = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB,
2416 FALSE, 8, width, height, width * 3, NULL, NULL);
2417 if (pix)
2418 {
2419 res = gdk_pixbuf_get_from_drawable(pix,
2420 pixmap ? pixmap : window, cmap,
2421 x, y, 0, 0, width, height);
2422 g_object_unref(pix);
2423 if (res) return (buf);
2424 }
2425 free(wbuf);
2426 return (NULL);
2427 }
2428
2429 #else /* if GTK_MAJOR_VERSION == 3 */
2430
wj_get_rgb_image(GdkWindow * window,cairo_surface_t * s,unsigned char * buf,int x,int y,int width,int height)2431 unsigned char *wj_get_rgb_image(GdkWindow *window, cairo_surface_t *s,
2432 unsigned char *buf, int x, int y, int width, int height)
2433 {
2434 GdkPixbuf *pix;
2435
2436 if (s) pix = gdk_pixbuf_get_from_surface(s, x, y, width, height);
2437 else pix = gdk_pixbuf_get_from_window(window, x, y, width, height);
2438 if (!pix) return (NULL);
2439
2440 if (!buf) buf = calloc(1, width * height * 3);
2441 if (buf) /* Copy data to 3bpp continuous buffer */
2442 {
2443 unsigned char *dest, *src;
2444 int x, y, d, ww, wh, nc, stride;
2445
2446 ww = gdk_pixbuf_get_width(pix);
2447 wh = gdk_pixbuf_get_height(pix);
2448 nc = gdk_pixbuf_get_n_channels(pix);
2449 stride = gdk_pixbuf_get_rowstride(pix);
2450
2451 /* If result is somehow larger (scaling?), clip it */
2452 if (ww > width) ww = width;
2453 if (wh > height) wh = height;
2454 src = gdk_pixbuf_get_pixels(pix);
2455 dest = buf;
2456 stride -= nc * ww;
2457 d = (width - ww) * 3;
2458 for (y = 0; y < wh; y++)
2459 {
2460 for (x = 0; x < ww; x++)
2461 {
2462 *dest++= src[0];
2463 *dest++= src[1];
2464 *dest++= src[2];
2465 src += nc;
2466 }
2467 src += stride;
2468 dest += d;
2469 }
2470 }
2471 g_object_unref(pix);
2472 return (buf);
2473 }
2474
2475 #endif
2476
2477 // Clipboard
2478
2479 #ifdef GDK_WINDOWING_WIN32
2480
2481 /* Detect if current clipboard belongs to something in the program itself;
2482 * on Windows, GDK is purposely lying to us about it, so use WinAPI instead */
internal_clipboard(int which)2483 int internal_clipboard(int which)
2484 {
2485 DWORD pid;
2486
2487 if (which) return (TRUE); // No "PRIMARY" clipboard exists on Windows
2488 GetWindowThreadProcessId(GetClipboardOwner(), &pid);
2489 return (pid == GetCurrentProcessId());
2490 }
2491
2492 #elif defined GDK_WINDOWING_QUARTZ
2493
2494 /* Detect if current clipboard belongs to something in the program itself;
2495 * on Quartz, GDK is halfbaked, so use a workaround instead */
internal_clipboard(int which)2496 int internal_clipboard(int which)
2497 {
2498 return (!!gtk_clipboard_get_owner(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)));
2499 }
2500
2501 #else
2502
2503 /* Detect if current clipboard belongs to something in the program itself */
internal_clipboard(int which)2504 int internal_clipboard(int which)
2505 {
2506 gpointer widget = NULL;
2507 GdkWindow *win = gdk_selection_owner_get(
2508 gdk_atom_intern(which ? "PRIMARY" : "CLIPBOARD", FALSE));
2509 if (!win) return (FALSE); // Unknown window
2510 gdk_window_get_user_data(win, &widget);
2511 return (!!widget); // Real widget or foreign window?
2512 }
2513
2514 #endif
2515
2516 // Clipboard pixmaps
2517
2518 #ifdef HAVE_PIXMAPS
2519
2520 /* Make code not compile if unthinkable happens */
2521 typedef char Mismatched_XID_type[2 * (sizeof(Pixmap) == sizeof(XID_type)) - 1];
2522
2523 /* It's unclear who should free clipboard pixmaps and when, so I do the same
2524 * thing Qt does, destroying the next-to-last allocated pixmap each time a new
2525 * one is allocated - WJ */
2526
2527 #if GTK_MAJOR_VERSION == 3
2528
export_pixmap(pixmap_info * p,int w,int h)2529 int export_pixmap(pixmap_info *p, int w, int h)
2530 {
2531 static cairo_surface_t *exported[2];
2532 cairo_surface_t *s = gdk_window_create_similar_surface(
2533 gtk_widget_get_window(main_window), CAIRO_CONTENT_COLOR, w, h);
2534
2535 if (cairo_surface_get_type(s) != CAIRO_SURFACE_TYPE_XLIB)
2536 {
2537 cairo_surface_destroy(s);
2538 return (FALSE);
2539 }
2540 if (exported[0])
2541 {
2542 if (exported[1])
2543 {
2544 /* Someone might have destroyed the X pixmap already,
2545 * so get ready to live through an X error */
2546 GdkDisplay *d = gtk_widget_get_display(main_window);
2547 gdk_x11_display_error_trap_push(d);
2548 cairo_surface_destroy(exported[1]);
2549 gdk_x11_display_error_trap_pop_ignored(d);
2550 }
2551 exported[1] = exported[0];
2552 }
2553 exported[0] = p->pm = s;
2554 p->w = w;
2555 p->h = h;
2556 p->depth = -1;
2557 p->xid = cairo_xlib_surface_get_drawable(s);
2558
2559 return (TRUE);
2560 }
2561
pixmap_put_rows(pixmap_info * p,unsigned char * src,int y,int cnt)2562 void pixmap_put_rows(pixmap_info *p, unsigned char *src, int y, int cnt)
2563 {
2564 cairo_surface_t *s;
2565 cairo_t *cr;
2566
2567 s = cairo_upload_rgb(p->pm, NULL, src, p->w, cnt, p->w * 3);
2568 cr = cairo_create(p->pm);
2569 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
2570 cairo_set_source_surface(cr, s, 0, y);
2571 cairo_rectangle(cr, 0, y, p->w, cnt);
2572 cairo_fill(cr);
2573 cairo_destroy(cr);
2574
2575 cairo_surface_fdestroy(s);
2576 }
2577
2578 #else /* if GTK_MAJOR_VERSION <= 2 */
2579
export_pixmap(pixmap_info * p,int w,int h)2580 int export_pixmap(pixmap_info *p, int w, int h)
2581 {
2582 static GdkPixmap *exported[2];
2583
2584 if (exported[0])
2585 {
2586 if (exported[1])
2587 {
2588 /* Someone might have destroyed the X pixmap already,
2589 * so get ready to live through an X error */
2590 gdk_error_trap_push();
2591 gdk_pixmap_unref(exported[1]);
2592 gdk_error_trap_pop();
2593 }
2594 exported[1] = exported[0];
2595 }
2596 exported[0] = p->pm = gdk_pixmap_new(main_window->window, w, h, -1);
2597 if (!exported[0]) return (FALSE);
2598
2599 p->w = w;
2600 p->h = h;
2601 p->depth = -1;
2602 p->xid = GDK_WINDOW_XWINDOW(exported[0]);
2603
2604 return (TRUE);
2605 }
2606
pixmap_put_rows(pixmap_info * p,unsigned char * src,int y,int cnt)2607 void pixmap_put_rows(pixmap_info *p, unsigned char *src, int y, int cnt)
2608 {
2609 gdk_draw_rgb_image(p->pm, main_window->style->black_gc,
2610 0, y, p->w, cnt, GDK_RGB_DITHER_NONE, src, p->w * 3);
2611 }
2612
2613 #endif
2614
2615 #endif /* HAVE_PIXMAPS */
2616
import_pixmap(pixmap_info * p,XID_type * xid)2617 int import_pixmap(pixmap_info *p, XID_type *xid)
2618 {
2619 if (xid) // Pixmap by ID
2620 {
2621 /* This ugly code imports X Window System's pixmaps; this allows mtPaint to
2622 * receive images from programs such as XPaint */
2623 #if (GTK_MAJOR_VERSION == 3) && defined GDK_WINDOWING_X11
2624 cairo_surface_t *s;
2625 GdkDisplay *d = gtk_widget_get_display(main_window);
2626 Display *disp = GDK_DISPLAY_XDISPLAY(d);
2627 XWindowAttributes attr;
2628 Window root;
2629 unsigned int x, y, w, h, bor, depth;
2630 int res;
2631
2632 gdk_x11_display_error_trap_push(d); // No guarantee that we got a valid pixmap
2633 res = XGetGeometry(disp, *xid, &root, &x, &y, &w, &h, &bor, &depth);
2634 if (res) res = XGetWindowAttributes(disp, root, &attr);
2635 gdk_x11_display_error_trap_pop_ignored(d);
2636 if (!res) return (FALSE);
2637
2638 s = cairo_xlib_surface_create(disp, *xid, attr.visual, w, h);
2639 if (cairo_surface_get_type(s) == CAIRO_SURFACE_TYPE_XLIB)
2640 {
2641 p->xid = *xid;
2642 p->pm = s;
2643 p->w = w;
2644 p->h = h;
2645 p->depth = depth;
2646 return (TRUE);
2647 }
2648 cairo_surface_destroy(s);
2649 #elif (GTK_MAJOR_VERSION == 1) || defined GDK_WINDOWING_X11
2650 int w, h, d, dd;
2651
2652 gdk_error_trap_push(); // No guarantee that we got a valid pixmap
2653 p->pm = gdk_pixmap_foreign_new(p->xid = *xid);
2654 gdk_error_trap_pop(); // The above call returns NULL on failure anyway
2655 if (!p->pm) return (FALSE);
2656 dd = gdk_visual_get_system()->depth;
2657 #if GTK_MAJOR_VERSION == 1
2658 gdk_window_get_geometry(p->pm, NULL, NULL, &w, &h, &d);
2659 #else /* #if GTK_MAJOR_VERSION == 2 */
2660 gdk_drawable_get_size(p->pm, &w, &h);
2661 d = gdk_drawable_get_depth(p->pm);
2662 #endif
2663 if ((d == 1) || (d == dd))
2664 {
2665 p->w = w;
2666 p->h = h;
2667 p->depth = d;
2668 return (TRUE);
2669 }
2670 drop_pixmap(p);
2671 #endif
2672 return (FALSE);
2673 }
2674 else // NULL means a screenshot
2675 {
2676 // !!! Should be the screen where gdk_get_default_root_window() is
2677 p->w = gdk_screen_width();
2678 p->h = gdk_screen_height();
2679 p->depth = 3;
2680 p->pm = NULL;
2681 p->xid = 0;
2682 }
2683 return (TRUE);
2684 }
2685
drop_pixmap(pixmap_info * p)2686 void drop_pixmap(pixmap_info *p)
2687 {
2688 #if (GTK_MAJOR_VERSION == 3) && defined GDK_WINDOWING_X11
2689 if (!p->pm) return;
2690 cairo_surface_destroy(p->pm);
2691 #elif (GTK_MAJOR_VERSION == 1) || defined GDK_WINDOWING_X11
2692 if (!p->pm) return;
2693 #if GTK_MAJOR_VERSION == 1
2694 /* Don't let gdk_pixmap_unref() destroy another process's pixmap -
2695 * implement freeing the GdkPixmap structure here instead */
2696 gdk_xid_table_remove(((GdkWindowPrivate *)p->pm)->xwindow);
2697 g_dataset_destroy(p->pm);
2698 g_free(p->pm);
2699 #else /* #if GTK_MAJOR_VERSION == 2 */
2700 gdk_pixmap_unref(p->pm);
2701 #endif
2702 #endif
2703 }
2704
pixmap_get_rows(pixmap_info * p,unsigned char * dest,int y,int cnt)2705 int pixmap_get_rows(pixmap_info *p, unsigned char *dest, int y, int cnt)
2706 {
2707 return (!!wj_get_rgb_image(p->depth == 1 ? NULL :
2708 #if GTK_MAJOR_VERSION == 1
2709 (GdkWindow *)&gdk_root_parent,
2710 #else /* #if GTK_MAJOR_VERSION >= 2 */
2711 gdk_get_default_root_window(),
2712 #endif
2713 p->pm, dest, 0, y, p->w, cnt));
2714 }
2715
2716 // Render stock icons to pixmaps
2717
2718 #if GTK_MAJOR_VERSION == 3
2719
2720 /* Actually loads a named icon, not stock */
render_stock_pixbuf(GtkWidget * widget,const gchar * stock_id)2721 static GdkPixbuf *render_stock_pixbuf(GtkWidget *widget, const gchar *stock_id)
2722 {
2723 GtkIconTheme *theme;
2724 gint w, h;
2725
2726 // !!! The theme need be modifiable by theme file loaded from prefs
2727 theme = gtk_icon_theme_get_for_screen(gtk_style_context_get_screen(
2728 gtk_widget_get_style_context(widget)));
2729 if (!theme) return (NULL); // Paranoia
2730 gtk_icon_size_lookup(GTK_ICON_SIZE_SMALL_TOOLBAR, &w, &h);
2731 return (gtk_icon_theme_load_icon(theme, stock_id, (w < h ? w : h),
2732 GTK_ICON_LOOKUP_USE_BUILTIN, NULL));
2733 }
2734
2735 #elif GTK_MAJOR_VERSION == 2
2736
render_stock_pixbuf(GtkWidget * widget,const gchar * stock_id)2737 static GdkPixbuf *render_stock_pixbuf(GtkWidget *widget, const gchar *stock_id)
2738 {
2739 GtkIconSet *icon_set;
2740
2741 /* !!! Doing this for widget itself in some cases fails (!) */
2742 icon_set = gtk_style_lookup_icon_set(main_window->style, stock_id);
2743 if (!icon_set) return (NULL);
2744 // !!! Is this "widget" here at all useful, or is "main_window" good enough?
2745 gtk_widget_ensure_style(widget);
2746 return (gtk_icon_set_render_icon(icon_set, widget->style,
2747 gtk_widget_get_direction(widget), GTK_WIDGET_STATE(widget),
2748 GTK_ICON_SIZE_SMALL_TOOLBAR, widget, NULL));
2749 }
2750
render_stock_pixmap(GtkWidget * widget,const gchar * stock_id,GdkBitmap ** mask)2751 GdkPixmap *render_stock_pixmap(GtkWidget *widget, const gchar *stock_id,
2752 GdkBitmap **mask)
2753 {
2754 GdkPixmap *pmap;
2755 GdkPixbuf *buf;
2756
2757 buf = render_stock_pixbuf(widget, stock_id);
2758 if (!buf) return (NULL);
2759 gdk_pixbuf_render_pixmap_and_mask_for_colormap(buf,
2760 gtk_widget_get_colormap(widget), &pmap, mask, 127);
2761 g_object_unref(buf);
2762 return (pmap);
2763 }
2764
2765 #endif
2766
2767 // Image widget
2768
2769 /* !!! GtkImage is broken on GTK+ 2.12.9 at least, with regard to pixmaps -
2770 * background gets corrupted when widget is made insensitive, so GtkPixmap is
2771 * the only choice in that case - WJ */
2772
2773 #if GTK_MAJOR_VERSION == 2
2774 /* Guard against different depth visuals */
xpm_realize(GtkWidget * widget,gpointer user_data)2775 static void xpm_realize(GtkWidget *widget, gpointer user_data)
2776 {
2777 if (gdk_drawable_get_depth(widget->window) !=
2778 gdk_drawable_get_depth(GTK_PIXMAP(widget)->pixmap))
2779 {
2780 GdkPixmap *icon, *mask;
2781 icon = gdk_pixmap_create_from_xpm_d(widget->window, &mask, NULL,
2782 user_data);
2783 gtk_pixmap_set(GTK_PIXMAP(widget), icon, mask);
2784 gdk_pixmap_unref(icon);
2785 gdk_pixmap_unref(mask);
2786 }
2787 }
2788 #endif
2789
xpm_image(XPM_TYPE xpm)2790 GtkWidget *xpm_image(XPM_TYPE xpm)
2791 {
2792 GtkWidget *widget;
2793 #if GTK_MAJOR_VERSION >= 2
2794 GdkPixbuf *buf;
2795 char name[256];
2796
2797 snprintf(name, sizeof(name), "mtpaint_%s", (char *)xpm[0]);
2798 buf = render_stock_pixbuf(main_window, name);
2799 if (buf) /* Found a themed icon - use it */
2800 {
2801 widget = gtk_image_new_from_pixbuf(buf);
2802 g_object_unref(buf);
2803 gtk_widget_show(widget);
2804 return (widget);
2805 }
2806 #endif
2807 /* Fall back to builtin XPM icon */
2808 #if GTK_MAJOR_VERSION == 3
2809 buf = gdk_pixbuf_new_from_xpm_data((const char **)xpm[1]);
2810 widget = gtk_image_new_from_pixbuf(buf);
2811 g_object_unref(buf);
2812 #else /* if GTK_MAJOR_VERSION <= 2 */
2813 {
2814 GdkPixmap *icon, *mask;
2815 icon = gdk_pixmap_create_from_xpm_d(main_window->window, &mask,
2816 #if GTK_MAJOR_VERSION == 2
2817 NULL, (char **)xpm[1]);
2818 #else /* if GTK_MAJOR_VERSION == 1 */
2819 NULL, xpm);
2820 #endif
2821 widget = gtk_pixmap_new(icon, mask);
2822 gdk_pixmap_unref(icon);
2823 gdk_pixmap_unref(mask);
2824 }
2825 #endif
2826 gtk_widget_show(widget);
2827 #if GTK_MAJOR_VERSION == 2
2828 gtk_signal_connect(GTK_OBJECT(widget), "realize",
2829 GTK_SIGNAL_FUNC(xpm_realize), (char **)xpm[1]);
2830 #endif
2831 return (widget);
2832 }
2833
2834 // Release outstanding pointer grabs
2835
2836 #if GTK_MAJOR_VERSION == 3
2837
release_grab()2838 int release_grab()
2839 {
2840 GdkDisplay *dp = gdk_display_get_default();
2841 GList *l, *ll;
2842 int res = FALSE;
2843
2844 ll = gdk_device_manager_list_devices(gdk_display_get_device_manager(dp),
2845 GDK_DEVICE_TYPE_MASTER);
2846 for (l = ll; l; l = l->next)
2847 {
2848 GdkDevice *dev = l->data;
2849 if ((gdk_device_get_source(dev) == GDK_SOURCE_MOUSE) &&
2850 gdk_display_device_is_grabbed(dp, dev))
2851 {
2852 gdk_device_ungrab(dev, GDK_CURRENT_TIME);
2853 res = TRUE;
2854 }
2855 }
2856 g_list_free(ll);
2857
2858 return (res);
2859 }
2860
2861 #else /* if GTK_MAJOR_VERSION <= 2 */
2862
release_grab()2863 int release_grab()
2864 {
2865 if (!gdk_pointer_is_grabbed()) return (FALSE);
2866 gdk_pointer_ungrab(GDK_CURRENT_TIME);
2867 return (TRUE);
2868 }
2869
2870 #endif
2871
2872 // Frame widget with passthrough scrolling
2873
2874 #if GTK_MAJOR_VERSION == 3
2875
2876 #define WJFRAME(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, wjframe_get_type(), wjframe)
2877 #define IS_WJFRAME(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, wjframe_get_type())
2878
2879 typedef struct
2880 {
2881 GtkBin bin; // Parent class
2882 GtkAdjustment *adjustments[2];
2883 GtkAllocation inside;
2884 } wjframe;
2885
2886 typedef struct
2887 {
2888 GtkBinClass parent_class;
2889 } wjframeClass;
2890
G_DEFINE_TYPE_WITH_CODE(wjframe,wjframe,GTK_TYPE_BIN,G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE,NULL))2891 G_DEFINE_TYPE_WITH_CODE(wjframe, wjframe, GTK_TYPE_BIN,
2892 G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL))
2893
2894 #define WJFRAME_SHADOW 1 /* Line width, limited only by common sense */
2895
2896 static gboolean wjframe_draw(GtkWidget *widget, cairo_t *cr)
2897 {
2898 wjframe *frame = WJFRAME(widget);
2899 /* !!! Using deprecated struct to avoid recalculating the colors */
2900 GtkStyle *style = gtk_widget_get_style(widget);
2901 GtkAllocation alloc;
2902 double dxy = WJFRAME_SHADOW * 0.5;
2903 int x, y, x1, y1;
2904
2905
2906 gtk_widget_get_allocation(widget, &alloc);
2907 x1 = (x = frame->inside.x - alloc.x) + frame->inside.width - 1;
2908 y1 = (y = frame->inside.y - alloc.y) + frame->inside.height - 1;
2909
2910 cairo_save(cr);
2911 cairo_set_line_width(cr, WJFRAME_SHADOW);
2912 cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
2913 gdk_cairo_set_source_color(cr, style->light + GTK_STATE_NORMAL);
2914 cairo_move_to(cr, x1 + dxy, y - dxy);
2915 cairo_line_to(cr, x1 + dxy, y1 + dxy); // Right
2916 cairo_line_to(cr, x - dxy, y1 + dxy); // Bottom
2917 cairo_stroke(cr);
2918 gdk_cairo_set_source_color(cr, style->dark + GTK_STATE_NORMAL);
2919 cairo_move_to(cr, x1 + dxy, y - dxy);
2920 cairo_line_to(cr, x - dxy, y - dxy); // Top
2921 cairo_line_to(cr, x - dxy, y1 + dxy); // Left
2922 cairo_stroke(cr);
2923 cairo_restore(cr);
2924
2925 /* To draw child widget */
2926 GTK_WIDGET_CLASS(wjframe_parent_class)->draw(widget, cr);
2927
2928 return (FALSE);
2929 }
2930
wjframe_size_allocate(GtkWidget * widget,GtkAllocation * allocation)2931 static void wjframe_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
2932 {
2933 wjframe *frame = WJFRAME(widget);
2934 GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
2935 int border = gtk_container_get_border_width(GTK_CONTAINER(widget)) +
2936 WJFRAME_SHADOW;
2937 GtkAllocation now_inside;
2938
2939 gtk_widget_set_allocation(widget, allocation);
2940 now_inside.x = allocation->x + border;
2941 now_inside.y = allocation->y + border;
2942 now_inside.width = MAX(allocation->width - border * 2, 1);
2943 now_inside.height = MAX(allocation->height - border * 2, 1);
2944
2945 /* Redraw if inside moved while visible */
2946 if (gtk_widget_get_mapped(widget) &&
2947 ((frame->inside.x ^ now_inside.x) |
2948 (frame->inside.y ^ now_inside.y) |
2949 (frame->inside.width ^ now_inside.width) |
2950 (frame->inside.height ^ now_inside.height)))
2951 gdk_window_invalidate_rect(gtk_widget_get_window(widget),
2952 allocation, FALSE);
2953 frame->inside = now_inside;
2954
2955 if (child && gtk_widget_get_visible(child))
2956 gtk_widget_size_allocate(child, &now_inside);
2957 }
2958
wjframe_get_size(GtkWidget * widget,gint vert,gint * min,gint * nat)2959 static void wjframe_get_size(GtkWidget *widget, gint vert, gint *min, gint *nat)
2960 {
2961 GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
2962 int border = gtk_container_get_border_width(GTK_CONTAINER(widget)) +
2963 WJFRAME_SHADOW;
2964 gint cmin = 0, cnat = 0;
2965
2966 if (child && gtk_widget_get_visible(child))
2967 (vert ? gtk_widget_get_preferred_height :
2968 gtk_widget_get_preferred_width)(child, &cmin, &cnat);
2969 *min = cmin + border * 2;
2970 *nat = cnat + border * 2;
2971 }
2972
wjframe_get_preferred_width(GtkWidget * widget,gint * min,gint * nat)2973 static void wjframe_get_preferred_width(GtkWidget *widget, gint *min, gint *nat)
2974 {
2975 wjframe_get_size(widget, FALSE, min, nat);
2976 }
2977
wjframe_get_preferred_height(GtkWidget * widget,gint * min,gint * nat)2978 static void wjframe_get_preferred_height(GtkWidget *widget, gint *min, gint *nat)
2979 {
2980 wjframe_get_size(widget, TRUE, min, nat);
2981 }
2982
wjframe_get_preferred_width_for_height(GtkWidget * widget,gint h,gint * min,gint * nat)2983 static void wjframe_get_preferred_width_for_height(GtkWidget *widget, gint h,
2984 gint *min, gint *nat)
2985 {
2986 wjframe_get_size(widget, FALSE, min, nat);
2987 }
2988
2989 /* Specialcase only height-for-width, same as GtkFrame does */
wjframe_get_preferred_height_for_width(GtkWidget * widget,gint w,gint * min,gint * nat)2990 static void wjframe_get_preferred_height_for_width(GtkWidget *widget, gint w,
2991 gint *min, gint *nat)
2992 {
2993 GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
2994 int border = gtk_container_get_border_width(GTK_CONTAINER(widget)) +
2995 WJFRAME_SHADOW;
2996 gint cmin = 0, cnat = 0;
2997
2998 if (child && gtk_widget_get_visible(child))
2999 gtk_widget_get_preferred_height_for_width(child, w - border * 2,
3000 &cmin, &cnat);
3001 *min = cmin + border * 2;
3002 *nat = cnat + border * 2;
3003 }
3004
wjframe_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)3005 static void wjframe_set_property(GObject *object, guint prop_id,
3006 const GValue *value, GParamSpec *pspec)
3007 {
3008 wjframe *frame = WJFRAME(object);
3009 GtkWidget *child = gtk_bin_get_child(GTK_BIN(object));
3010
3011 if ((prop_id == P_HADJ) || (prop_id == P_VADJ))
3012 {
3013 /* Cache the object */
3014 GtkAdjustment *adj, **slot = frame->adjustments + prop_id - P_HADJ;
3015
3016 adj = g_value_get_object(value);
3017 if (adj) g_object_ref(adj);
3018 if (*slot) g_object_unref(*slot);
3019 *slot = adj;
3020 }
3021 // !!! React to "*scroll-policy" as an invalid ID when empty
3022 else if (child && ((prop_id == P_HSCP) || (prop_id == P_VSCP)));
3023 else
3024 {
3025 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
3026 return;
3027 }
3028 /* Send the thing to child to handle */
3029 if (child) g_object_set_property(G_OBJECT(child), scroll_pnames[prop_id], value);
3030 }
3031
wjframe_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)3032 static void wjframe_get_property(GObject *object, guint prop_id, GValue *value,
3033 GParamSpec *pspec)
3034 {
3035 wjframe *frame = WJFRAME(object);
3036
3037 if ((prop_id == P_HADJ) || (prop_id == P_VADJ))
3038 /* Returning cached object */
3039 g_value_set_object(value, frame->adjustments[prop_id - P_HADJ]);
3040 else if ((prop_id == P_HSCP) || (prop_id == P_VSCP))
3041 {
3042 /* Proxying for child */
3043 GtkWidget *child = gtk_bin_get_child(GTK_BIN(object));
3044 if (child) g_object_get_property(G_OBJECT(child),
3045 scroll_pnames[prop_id], value);
3046 else g_value_set_enum(value, GTK_SCROLL_NATURAL); // Default
3047 }
3048 else G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
3049 }
3050
wjframe_add(GtkContainer * container,GtkWidget * child)3051 static void wjframe_add(GtkContainer *container, GtkWidget *child)
3052 {
3053 wjframe *frame = WJFRAME(container);
3054
3055 if (gtk_bin_get_child(GTK_BIN(container))) return;
3056 GTK_CONTAINER_CLASS(wjframe_parent_class)->add(container, child);
3057
3058 /* Set only existing adjustments */
3059 if (frame->adjustments[0] || frame->adjustments[1])
3060 g_object_set(child, "hadjustment", frame->adjustments[0],
3061 "vadjustment", frame->adjustments[1], NULL);
3062 }
3063
wjframe_remove(GtkContainer * container,GtkWidget * child)3064 static void wjframe_remove(GtkContainer *container, GtkWidget *child)
3065 {
3066 wjframe *frame = WJFRAME(container);
3067 GtkWidget *child0 = gtk_bin_get_child(GTK_BIN(container));
3068
3069 if (!child || (child0 != child)) return;
3070
3071 /* Remove only existing adjustments */
3072 if (frame->adjustments[0] || frame->adjustments[1])
3073 g_object_set(child, "hadjustment", NULL, "vadjustment", NULL, NULL);
3074
3075 GTK_CONTAINER_CLASS(wjframe_parent_class)->remove(container, child);
3076 }
3077
wjframe_destroy(GtkWidget * widget)3078 static void wjframe_destroy(GtkWidget *widget)
3079 {
3080 wjframe *frame = WJFRAME(widget);
3081
3082 if (frame->adjustments[0]) g_object_unref(frame->adjustments[0]);
3083 if (frame->adjustments[1]) g_object_unref(frame->adjustments[1]);
3084 frame->adjustments[0] = frame->adjustments[1] = NULL;
3085 GTK_WIDGET_CLASS(wjframe_parent_class)->destroy(widget);
3086 }
3087
wjframe_class_init(wjframeClass * class)3088 static void wjframe_class_init(wjframeClass *class)
3089 {
3090 GtkContainerClass *cclass = GTK_CONTAINER_CLASS(class);
3091 GtkWidgetClass *wclass = GTK_WIDGET_CLASS(class);
3092 GObjectClass *oclass = G_OBJECT_CLASS(class);
3093
3094 oclass->set_property = wjframe_set_property;
3095 oclass->get_property = wjframe_get_property;
3096 wclass->destroy = wjframe_destroy;
3097 wclass->draw = wjframe_draw;
3098 wclass->size_allocate = wjframe_size_allocate;
3099 wclass->get_preferred_width = wjframe_get_preferred_width;
3100 wclass->get_preferred_height = wjframe_get_preferred_height;
3101 wclass->get_preferred_width_for_height = wjframe_get_preferred_width_for_height;
3102 wclass->get_preferred_height_for_width = wjframe_get_preferred_height_for_width;
3103 /* !!! Leave my frame alone */
3104 wclass->style_updated = NULL;
3105 cclass->add = wjframe_add;
3106 cclass->remove = wjframe_remove;
3107
3108 g_object_class_override_property(oclass, P_HADJ, "hadjustment");
3109 g_object_class_override_property(oclass, P_VADJ, "vadjustment");
3110 g_object_class_override_property(oclass, P_HSCP, "hscroll-policy");
3111 g_object_class_override_property(oclass, P_VSCP, "vscroll-policy");
3112 }
3113
wjframe_init(wjframe * frame)3114 static void wjframe_init(wjframe *frame)
3115 {
3116 // gtk_widget_set_has_window(GTK_WIDGET(frame), FALSE); // GtkBin done it
3117 frame->adjustments[0] = frame->adjustments[1] = NULL;
3118 }
3119
3120 #else /* if GTK_MAJOR_VERSION <= 2 */
3121
3122 /* !!! Windows builds of GTK+ are made with G_ENABLE_DEBUG, which means also
3123 * marshallers checking what passes through. Since "OBJECT" is, from their
3124 * point of view, not "POINTER", we need our own marshaller without the checks,
3125 * or our "set-scroll-adjustments" handlers won't receive anything but NULLs.
3126 * Just in case someone does the same on Unix, we use our marshaller with GTK+2
3127 * regardless of the host OS - WJ */
3128
3129 /* #if defined GDK_WINDOWING_WIN32 */
3130 #if GTK_MAJOR_VERSION == 2
3131
3132 /* Function autogenerated by "glib-genmarshal" utility, then improved a bit - WJ */
3133 #define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer
unchecked_gtk_marshal_VOID__POINTER_POINTER(GClosure * closure,GValue * return_value,guint n_param_values,const GValue * param_values,gpointer invocation_hint,gpointer marshal_data)3134 static void unchecked_gtk_marshal_VOID__POINTER_POINTER(GClosure *closure,
3135 GValue *return_value, guint n_param_values, const GValue *param_values,
3136 gpointer invocation_hint, gpointer marshal_data)
3137 {
3138 void (*callback)(gpointer data1, gpointer arg1, gpointer arg2, gpointer data2);
3139 gpointer data1, data2;
3140
3141 if (n_param_values != 3) return;
3142
3143 data1 = data2 = g_value_peek_pointer(param_values + 0);
3144 if (G_CCLOSURE_SWAP_DATA(closure)) data1 = closure->data;
3145 else data2 = closure->data;
3146 callback = marshal_data ? marshal_data : ((GCClosure *)closure)->callback;
3147
3148 callback(data1, g_marshal_value_peek_pointer(param_values + 1),
3149 g_marshal_value_peek_pointer(param_values + 2), data2);
3150 }
3151
3152 #undef gtk_marshal_NONE__POINTER_POINTER
3153 #define gtk_marshal_NONE__POINTER_POINTER unchecked_gtk_marshal_VOID__POINTER_POINTER
3154
3155 #endif
3156
3157 #define WJFRAME(obj) GTK_CHECK_CAST(obj, wjframe_get_type(), wjframe)
3158 #define IS_WJFRAME(obj) GTK_CHECK_TYPE(obj, wjframe_get_type())
3159
3160 typedef struct
3161 {
3162 GtkBin bin; // Parent class
3163 GtkAdjustment *adjustments[2];
3164 } wjframe;
3165
3166 typedef struct
3167 {
3168 GtkBinClass parent_class;
3169 void (*set_scroll_adjustments)(wjframe *frame,
3170 GtkAdjustment *hadjustment, GtkAdjustment *vadjustment);
3171 } wjframeClass;
3172
3173 static GtkBinClass *bin_class;
3174 static GtkType wjframe_type;
3175
3176 #define WJFRAME_SHADOW 1
3177
3178 static GtkType wjframe_get_type();
3179
wjframe_realize(GtkWidget * widget)3180 static void wjframe_realize(GtkWidget *widget)
3181 {
3182 GdkWindowAttr attrs;
3183 int border = GTK_CONTAINER(widget)->border_width;
3184
3185
3186 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
3187
3188 /* Make widget window */
3189 attrs.x = widget->allocation.x + border;
3190 attrs.y = widget->allocation.y + border;
3191 attrs.width = widget->allocation.width - 2 * border;
3192 attrs.height = widget->allocation.height - 2 * border;
3193 attrs.window_type = GDK_WINDOW_CHILD;
3194 attrs.wclass = GDK_INPUT_OUTPUT;
3195 attrs.visual = gtk_widget_get_visual(widget);
3196 attrs.colormap = gtk_widget_get_colormap(widget);
3197 // Window exists only to render the shadow
3198 attrs.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
3199 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
3200 &attrs, GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP);
3201 gdk_window_set_user_data(widget->window, widget);
3202
3203 widget->style = gtk_style_attach(widget->style, widget->window);
3204 /* Background clear is for wimps :-) */
3205 gdk_window_set_back_pixmap(widget->window, NULL, FALSE);
3206 // !!! Do this instead if the widget is ever used for non-canvaslike stuff
3207 // gtk_style_set_background(widget->style, widget->window, GTK_STATE_NORMAL);
3208 }
3209
wjframe_paint(GtkWidget * widget,GdkRectangle * area)3210 static void wjframe_paint(GtkWidget *widget, GdkRectangle *area)
3211 {
3212 GdkWindow *window = widget->window;
3213 GdkGC *light, *dark;
3214 gint w, h;
3215 int x1, y1;
3216
3217 if (!window || !widget->style) return;
3218 gdk_window_get_size(window, &w, &h);
3219
3220 #if 0 /* !!! Useless with canvaslike widgets */
3221 if ((w > WJFRAME_SHADOW * 2) && (h > WJFRAME_SHADOW * 2))
3222 gtk_paint_flat_box(widget->style, window,
3223 widget->state, GTK_SHADOW_NONE,
3224 area, widget, NULL,
3225 WJFRAME_SHADOW, WJFRAME_SHADOW,
3226 w - WJFRAME_SHADOW * 2, h - WJFRAME_SHADOW * 2);
3227 #endif
3228
3229 /* State, shadow, and widget type are hardcoded */
3230 light = widget->style->light_gc[GTK_STATE_NORMAL];
3231 dark = widget->style->dark_gc[GTK_STATE_NORMAL];
3232 gdk_gc_set_clip_rectangle(light, area);
3233 gdk_gc_set_clip_rectangle(dark, area);
3234
3235 x1 = w - 1; y1 = h - 1;
3236 gdk_draw_line(window, light, 0, y1, x1, y1);
3237 gdk_draw_line(window, light, x1, 0, x1, y1);
3238 gdk_draw_line(window, dark, 0, 0, x1, 0);
3239 gdk_draw_line(window, dark, 0, 0, 0, y1);
3240
3241 gdk_gc_set_clip_rectangle(light, NULL);
3242 gdk_gc_set_clip_rectangle(dark, NULL);
3243 }
3244
3245 #if GTK_MAJOR_VERSION == 1
3246
wjframe_draw(GtkWidget * widget,GdkRectangle * area)3247 static void wjframe_draw(GtkWidget *widget, GdkRectangle *area)
3248 {
3249 GdkRectangle tmp, child;
3250 GtkBin *bin = GTK_BIN(widget);
3251 int border = GTK_CONTAINER(widget)->border_width;
3252
3253 if (!area || !GTK_WIDGET_DRAWABLE(widget)) return;
3254
3255 tmp = *area;
3256 tmp.x -= border; tmp.y -= border;
3257 wjframe_paint(widget, &tmp);
3258
3259 if (bin->child && gtk_widget_intersect(bin->child, &tmp, &child))
3260 gtk_widget_draw(bin->child, &child);
3261 }
3262
wjframe_expose(GtkWidget * widget,GdkEventExpose * event)3263 static gboolean wjframe_expose(GtkWidget *widget, GdkEventExpose *event)
3264 {
3265 GtkBin *bin = GTK_BIN(widget);
3266
3267 if (!GTK_WIDGET_DRAWABLE(widget)) return (FALSE);
3268 wjframe_paint(widget, &event->area);
3269
3270 if (bin->child && GTK_WIDGET_NO_WINDOW(bin->child))
3271 {
3272 GdkEventExpose tmevent = *event;
3273 if (gtk_widget_intersect(bin->child, &event->area, &tmevent.area))
3274 gtk_widget_event(bin->child, (GdkEvent *)&tmevent);
3275 }
3276 return (FALSE);
3277 }
3278
3279 #else /* if GTK_MAJOR_VERSION == 2 */
3280
wjframe_expose(GtkWidget * widget,GdkEventExpose * event)3281 static gboolean wjframe_expose(GtkWidget *widget, GdkEventExpose *event)
3282 {
3283 if (!GTK_WIDGET_DRAWABLE(widget)) return (FALSE);
3284 wjframe_paint(widget, &event->area);
3285
3286 GTK_WIDGET_CLASS(bin_class)->expose_event(widget, event);
3287 return (FALSE);
3288 }
3289
3290 #endif
3291
wjframe_size_request(GtkWidget * widget,GtkRequisition * requisition)3292 static void wjframe_size_request(GtkWidget *widget, GtkRequisition *requisition)
3293 {
3294 GtkRequisition req;
3295 GtkBin *bin = GTK_BIN(widget);
3296 int border = GTK_CONTAINER(widget)->border_width;
3297
3298 requisition->width = requisition->height = (WJFRAME_SHADOW + border) * 2;
3299 if (bin->child && GTK_WIDGET_VISIBLE(bin->child))
3300 {
3301 gtk_widget_size_request(bin->child, &req);
3302 requisition->width += req.width;
3303 requisition->height += req.height;
3304 }
3305 }
3306
wjframe_size_allocate(GtkWidget * widget,GtkAllocation * allocation)3307 static void wjframe_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
3308 {
3309 GtkAllocation alloc;
3310 GtkBin *bin = GTK_BIN(widget);
3311 int border = GTK_CONTAINER(widget)->border_width;
3312
3313
3314 widget->allocation = *allocation;
3315 alloc.x = alloc.y = WJFRAME_SHADOW;
3316 alloc.width = MAX(allocation->width - (WJFRAME_SHADOW + border) * 2, 0);
3317 alloc.height = MAX(allocation->height - (WJFRAME_SHADOW + border) * 2, 0);
3318
3319 if (GTK_WIDGET_REALIZED(widget)) gdk_window_move_resize(widget->window,
3320 allocation->x + border, allocation->y + border,
3321 allocation->width - border * 2, allocation->height - border * 2);
3322
3323 if (bin->child) gtk_widget_size_allocate(bin->child, &alloc);
3324 }
3325
wjframe_set_adjustments(wjframe * frame,GtkAdjustment * hadjustment,GtkAdjustment * vadjustment)3326 static void wjframe_set_adjustments(wjframe *frame,
3327 GtkAdjustment *hadjustment, GtkAdjustment *vadjustment)
3328 {
3329 if (hadjustment) gtk_object_ref(GTK_OBJECT(hadjustment));
3330 if (frame->adjustments[0])
3331 gtk_object_unref(GTK_OBJECT(frame->adjustments[0]));
3332 frame->adjustments[0] = hadjustment;
3333 if (vadjustment) gtk_object_ref(GTK_OBJECT(vadjustment));
3334 if (frame->adjustments[1])
3335 gtk_object_unref(GTK_OBJECT(frame->adjustments[1]));
3336 frame->adjustments[1] = vadjustment;
3337 }
3338
wjframe_set_scroll_adjustments(wjframe * frame,GtkAdjustment * hadjustment,GtkAdjustment * vadjustment)3339 static void wjframe_set_scroll_adjustments(wjframe *frame,
3340 GtkAdjustment *hadjustment, GtkAdjustment *vadjustment)
3341 {
3342 GtkBin *bin = GTK_BIN(frame);
3343
3344 if ((hadjustment == frame->adjustments[0]) &&
3345 (vadjustment == frame->adjustments[1])) return;
3346
3347 wjframe_set_adjustments(frame, hadjustment, vadjustment);
3348
3349 if (bin->child) gtk_widget_set_scroll_adjustments(bin->child,
3350 hadjustment, vadjustment);
3351 }
3352
wjframe_add(GtkContainer * container,GtkWidget * child)3353 static void wjframe_add(GtkContainer *container, GtkWidget *child)
3354 {
3355 wjframe *frame = WJFRAME(container);
3356 GtkBin *bin = GTK_BIN(container);
3357 GtkWidget *widget = GTK_WIDGET(container);
3358
3359
3360 if (bin->child) return;
3361 bin->child = child;
3362 gtk_widget_set_parent(child, widget);
3363
3364 /* Set only existing adjustments */
3365 if (frame->adjustments[0] || frame->adjustments[1])
3366 gtk_widget_set_scroll_adjustments(child,
3367 frame->adjustments[0], frame->adjustments[1]);
3368
3369 #if GTK_MAJOR_VERSION == 1
3370 if (GTK_WIDGET_REALIZED(widget)) gtk_widget_realize(child);
3371
3372 if (GTK_WIDGET_VISIBLE(widget) && GTK_WIDGET_VISIBLE(child))
3373 {
3374 if (GTK_WIDGET_MAPPED(widget)) gtk_widget_map(child);
3375 gtk_widget_queue_resize(child);
3376 }
3377 #endif
3378 }
3379
wjframe_remove(GtkContainer * container,GtkWidget * child)3380 static void wjframe_remove(GtkContainer *container, GtkWidget *child)
3381 {
3382 wjframe *frame = WJFRAME(container);
3383 GtkBin *bin = GTK_BIN(container);
3384
3385
3386 if (!child || (bin->child != child)) return;
3387
3388 /* Remove only existing adjustments */
3389 if (frame->adjustments[0] || frame->adjustments[1])
3390 gtk_widget_set_scroll_adjustments(child, NULL, NULL);
3391
3392 GTK_CONTAINER_CLASS(bin_class)->remove(container, child);
3393 }
3394
wjframe_destroy(GtkObject * object)3395 static void wjframe_destroy(GtkObject *object)
3396 {
3397 wjframe_set_adjustments(WJFRAME(object), NULL, NULL);
3398
3399 GTK_OBJECT_CLASS(bin_class)->destroy(object);
3400 }
3401
wjframe_class_init(wjframeClass * class)3402 static void wjframe_class_init(wjframeClass *class)
3403 {
3404 GtkWidgetClass *wclass = GTK_WIDGET_CLASS(class);
3405 GtkContainerClass *cclass = GTK_CONTAINER_CLASS(class);
3406
3407 bin_class = gtk_type_class(GTK_TYPE_BIN);
3408 GTK_OBJECT_CLASS(class)->destroy = wjframe_destroy;
3409 #if GTK_MAJOR_VERSION == 1
3410 wclass->draw = wjframe_draw;
3411 #endif
3412 wclass->realize = wjframe_realize;
3413 wclass->expose_event = wjframe_expose;
3414 wclass->size_request = wjframe_size_request;
3415 wclass->size_allocate = wjframe_size_allocate;
3416 // !!! Default "style_set" handler can reenable background clear
3417 wclass->style_set = NULL;
3418 cclass->add = wjframe_add;
3419 cclass->remove = wjframe_remove;
3420 class->set_scroll_adjustments = wjframe_set_scroll_adjustments;
3421
3422 wclass->set_scroll_adjustments_signal = gtk_signal_new(
3423 "set_scroll_adjustments", GTK_RUN_LAST, wjframe_type,
3424 GTK_SIGNAL_OFFSET(wjframeClass, set_scroll_adjustments),
3425 gtk_marshal_NONE__POINTER_POINTER, GTK_TYPE_NONE,
3426 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
3427 }
3428
wjframe_init(wjframe * frame)3429 static void wjframe_init(wjframe *frame)
3430 {
3431 GTK_WIDGET_UNSET_FLAGS(frame, GTK_NO_WINDOW);
3432 #if GTK_MAJOR_VERSION == 2
3433 // !!! Would only waste time, with canvaslike child widgets
3434 GTK_WIDGET_UNSET_FLAGS(frame, GTK_DOUBLE_BUFFERED);
3435 #endif
3436 frame->adjustments[0] = frame->adjustments[1] = NULL;
3437 }
3438
wjframe_get_type()3439 static GtkType wjframe_get_type()
3440 {
3441 if (!wjframe_type)
3442 {
3443 static const GtkTypeInfo wjframe_info = {
3444 "wjFrame", sizeof(wjframe), sizeof(wjframeClass),
3445 (GtkClassInitFunc)wjframe_class_init,
3446 (GtkObjectInitFunc)wjframe_init,
3447 NULL, NULL, NULL };
3448 wjframe_type = gtk_type_unique(GTK_TYPE_BIN, &wjframe_info);
3449 }
3450
3451 return (wjframe_type);
3452 }
3453
3454 #endif /* GTK+1&2 */
3455
wjframe_new()3456 GtkWidget *wjframe_new()
3457 {
3458 return (gtk_widget_new(wjframe_get_type(), NULL));
3459 }
3460
3461 // Scrollable canvas widget
3462
3463 #if GTK_MAJOR_VERSION == 3
3464
3465 #define WCACHE_STEP 64 /* Dimensions are multiples of this */
3466 #define WCACHE_FRAC 2 /* Can be this larger than necessary */
3467
3468 typedef struct {
3469 cairo_surface_t *s; // Surface
3470 int xy[4]; // Viewport
3471 int dx; // X offset
3472 int dy; // Y offset
3473 int w; // Cache line width
3474 int h; // Cache height including boundary rows
3475 int scale; // Window scale factor
3476 } wcache;
3477
3478 // Create cache for viewport
wcache_init(wcache * cache,int * vport,GdkWindow * win)3479 static void wcache_init(wcache *cache, int *vport, GdkWindow *win)
3480 {
3481 cache->w = vport[2] - vport[0] + WCACHE_STEP - 1;
3482 cache->h = vport[3] - vport[1] + WCACHE_STEP - 1;
3483 cache->w -= cache->w % WCACHE_STEP;
3484 cache->h -= cache->h % WCACHE_STEP;
3485 copy4(cache->xy, vport);
3486 cache->dx = cache->dy = 0;
3487 cache->scale = gdk_window_get_scale_factor(win);
3488 cache->s = gdk_window_create_similar_surface(win,
3489 CAIRO_CONTENT_COLOR, cache->w, cache->h);
3490 }
3491
3492 // Check if cache need be replaced, align it to vport if not
wcache_check(wcache * cache,int * vport,int empty)3493 static int wcache_check(wcache *cache, int *vport, int empty)
3494 {
3495 int tw = vport[2] - vport[0], th = vport[3] - vport[1];
3496
3497 if (!cache->s) return (TRUE);
3498 while ((tw <= cache->w) && (th <= cache->h))
3499 {
3500 tw += WCACHE_STEP - 1; tw -= tw % WCACHE_STEP;
3501 th += WCACHE_STEP - 1; th -= th % WCACHE_STEP;
3502 if (tw * th * WCACHE_FRAC < cache->w * cache->h) break;
3503 /* Adjust for new vport */
3504 if (empty)
3505 {
3506 cache->dx = 0;
3507 cache->dy = 0;
3508 }
3509 else if (memcmp(cache->xy, vport, sizeof(cache->xy)))
3510 {
3511 cache->dx = floor_mod(cache->dx + vport[0] - cache->xy[0],
3512 cache->w);
3513 cache->dy = floor_mod(cache->dy + vport[1] - cache->xy[1],
3514 cache->h);
3515 }
3516 copy4(cache->xy, vport);
3517
3518 return (FALSE); // Leave be
3519 }
3520 /* Drop the cache if no data inside */
3521 if (empty)
3522 {
3523 cairo_surface_fdestroy(cache->s);
3524 cache->s = NULL;
3525 }
3526 return (TRUE);
3527 }
3528
3529 // Move data from old cache to new
wcache_move(wcache * new,wcache * old)3530 static void wcache_move(wcache *new, wcache *old)
3531 {
3532 int rxy[4];
3533 cairo_t *cr;
3534
3535 if (!old->s) return;
3536 /* Copy matching part of old's spans into new */
3537 if (clip(rxy, old->xy[0], old->xy[1], old->xy[2], old->xy[3], new->xy))
3538 {
3539 cr = cairo_create(new->s);
3540 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
3541 cairo_set_source_surface(cr, old->s, old->xy[0] - new->xy[0] - old->dx,
3542 old->xy[1] - new->xy[1] - old->dy);
3543 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
3544 cairo_rectangle(cr, rxy[0] - new->xy[0], rxy[1] - new->xy[1],
3545 rxy[2] - rxy[0], rxy[3] - rxy[1]);
3546 cairo_fill(cr);
3547 cairo_destroy(cr);
3548 }
3549
3550 /* Drop the old cache */
3551 cairo_surface_fdestroy(old->s);
3552 old->s = NULL;
3553 }
3554
wcache_render(wcache * cache,cairo_t * cr)3555 static void wcache_render(wcache *cache, cairo_t *cr)
3556 {
3557 int h = cache->xy[3] - cache->xy[1], w = cache->xy[2] - cache->xy[0];
3558
3559 cairo_save(cr);
3560 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
3561 cairo_set_source_surface(cr, cache->s, -cache->dx, -cache->dy);
3562 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
3563 cairo_rectangle(cr, 0, 0, w, h);
3564 cairo_fill(cr);
3565 cairo_restore(cr);
3566 }
3567
3568 #define WJCANVAS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, wjcanvas_get_type(), wjcanvas)
3569 #define IS_WJCANVAS(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, wjcanvas_get_type())
3570
3571 typedef void (*wjc_expose_f)(GtkWidget *widget, cairo_region_t *clip, gpointer user_data);
3572
3573 typedef struct
3574 {
3575 GtkWidget widget; // Parent class
3576 GtkAdjustment *adjustments[2];
3577 cairo_region_t *r; // Cached region
3578 int xy[4]; // Viewport
3579 int size[2]; // Requested (virtual) size
3580 int resize; // Resize was requested
3581 int resizing; // Requested resize is happening
3582 guint32 scrolltime; // For autoscroll rate-limiting
3583 wcache cache; // Pixel cache
3584 wjc_expose_f expose; // Drawing function
3585 gpointer udata; // Link to slot
3586 } wjcanvas;
3587
3588 typedef struct
3589 {
3590 GtkWidgetClass parent_class;
3591 } wjcanvasClass;
3592
G_DEFINE_TYPE_WITH_CODE(wjcanvas,wjcanvas,GTK_TYPE_WIDGET,G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE,NULL))3593 G_DEFINE_TYPE_WITH_CODE(wjcanvas, wjcanvas, GTK_TYPE_WIDGET,
3594 G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL))
3595
3596 static gboolean wjcanvas_draw(GtkWidget *widget, cairo_t *cr)
3597 {
3598 wjcanvas *canvas = WJCANVAS(widget);
3599 cairo_region_t *clip;
3600 cairo_rectangle_list_t *rl;
3601 cairo_rectangle_int_t re;
3602 wcache ncache;
3603 int i;
3604
3605 /* Check if anything useful remains in cache */
3606 if (canvas->r)
3607 {
3608 cairo_rectangle_int_t vport = { canvas->xy[0], canvas->xy[1],
3609 canvas->xy[2] - canvas->xy[0], canvas->xy[3] - canvas->xy[1] };
3610 cairo_region_intersect_rectangle(canvas->r, &vport);
3611 if (cairo_region_is_empty(canvas->r))
3612 {
3613 cairo_region_destroy(canvas->r);
3614 canvas->r = NULL;
3615 }
3616 }
3617
3618 /* Check if need a new cache */
3619 if (wcache_check(&canvas->cache, canvas->xy, !canvas->r))
3620 {
3621 wcache_init(&ncache, canvas->xy, gtk_widget_get_window(widget)); // Create it
3622 wcache_move(&ncache, &canvas->cache); // Move contents if needed
3623 canvas->cache = ncache;
3624 }
3625
3626 /* Convert clip to image-space region */
3627 clip = cairo_region_create();
3628 rl = cairo_copy_clip_rectangle_list(cr);
3629 if (rl->status != CAIRO_STATUS_SUCCESS) // Paranoia fallback
3630 {
3631 // The two rectangle types documented as identical in GTK+3 docs
3632 if (gdk_cairo_get_clip_rectangle(cr, (GdkRectangle*)&re))
3633 cairo_region_union_rectangle(clip, &re);
3634 }
3635 else for (i = 0; i < rl->num_rectangles; i++)
3636 {
3637 cairo_rectangle_t *rd = rl->rectangles + i;
3638 // GTK+3 assumes the values are converted ints
3639 re.x = rd->x; re.y = rd->y;
3640 re.width = rd->width; re.height = rd->height;
3641 cairo_region_union_rectangle(clip, &re);
3642 }
3643 cairo_rectangle_list_destroy(rl);
3644 cairo_region_translate(clip, canvas->xy[0], canvas->xy[1]);
3645
3646 /* Check if we need draw anything anew */
3647 if (canvas->r) cairo_region_subtract(clip, canvas->r);
3648 if (!cairo_region_is_empty(clip) && canvas->expose) // Do nothing if unset
3649 canvas->expose((GtkWidget *)canvas, clip, canvas->udata);
3650 cairo_region_destroy(clip);
3651
3652 wcache_render(&canvas->cache, cr);
3653
3654 return (FALSE);
3655 }
3656
wjcanvas_send_configure(GtkWidget * widget,GtkAllocation * alloc)3657 static void wjcanvas_send_configure(GtkWidget *widget, GtkAllocation *alloc)
3658 {
3659 GdkEvent *event = gdk_event_new(GDK_CONFIGURE);
3660
3661 event->configure.window = g_object_ref(gtk_widget_get_window(widget));
3662 event->configure.send_event = TRUE;
3663 event->configure.x = alloc->x;
3664 event->configure.y = alloc->y;
3665 event->configure.width = alloc->width;
3666 event->configure.height = alloc->height;
3667
3668 gtk_widget_event(widget, event);
3669 gdk_event_free(event);
3670 }
3671
wjcanvas_realize(GtkWidget * widget)3672 static void wjcanvas_realize(GtkWidget *widget)
3673 {
3674 // static const GdkRGBA black = { 0, 0, 0, 0 };
3675 GdkWindow *win;
3676 GdkWindowAttr attrs;
3677 GtkAllocation alloc;
3678
3679 gtk_widget_set_realized(widget, TRUE);
3680 gtk_widget_get_allocation(widget, &alloc);
3681
3682 attrs.x = alloc.x;
3683 attrs.y = alloc.y;
3684 attrs.width = alloc.width;
3685 attrs.height = alloc.height;
3686 attrs.window_type = GDK_WINDOW_CHILD;
3687 attrs.wclass = GDK_INPUT_OUTPUT;
3688 attrs.visual = gtk_widget_get_visual(widget);
3689 /* !!! GtkViewport also sets GDK_TOUCH_MASK and GDK_SMOOTH_SCROLL_MASK,
3690 * but the latter blocks non-smooth scroll events if device sends
3691 * smooth ones, and the former, I do not (yet?) handle anyway - WJ */
3692 attrs.event_mask = gtk_widget_get_events(widget) | GDK_SCROLL_MASK;
3693 /* !!! GDK_EXPOSURE_MASK seems really not be needed (as advertised),
3694 * despite GtkDrawingArea still having it */
3695 win = gdk_window_new(gtk_widget_get_parent_window(widget),
3696 &attrs, GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);
3697 /* !!! Versions without this (below 3.12) are useless */
3698 gdk_window_set_event_compression(win, FALSE);
3699
3700 gtk_widget_register_window(widget, win);
3701 gtk_widget_set_window(widget, win);
3702
3703 // gdk_window_set_background_rgba(win, &black);
3704 // !!! In hope this (parent's bkg) isn't drawn twice
3705 gdk_window_set_background_pattern(win, NULL);
3706
3707 /* Replicate behaviour of GtkDrawingCanvas */
3708 wjcanvas_send_configure(widget, &alloc);
3709 }
3710
wjcanvas_readjust(wjcanvas * canvas,int which,GtkAllocation * alloc)3711 static int wjcanvas_readjust(wjcanvas *canvas, int which, GtkAllocation *alloc)
3712 {
3713 GtkAdjustment *adj = canvas->adjustments[which];
3714 int sz, wp;
3715 double oldv, newv;
3716
3717 oldv = gtk_adjustment_get_value(adj);
3718 wp = which ? alloc->height : alloc->width;
3719 sz = canvas->size[which];
3720 if (sz < wp) sz = wp;
3721 newv = oldv < 0.0 ? 0.0 : oldv > sz - wp ? sz - wp : oldv;
3722 gtk_adjustment_configure(adj, newv, 0, sz, wp * 0.1, wp * 0.9, wp);
3723
3724 return (newv != oldv);
3725 }
3726
wjcanvas_set_extents(wjcanvas * canvas,GtkAllocation * alloc)3727 static void wjcanvas_set_extents(wjcanvas *canvas, GtkAllocation *alloc)
3728 {
3729 canvas->xy[2] = alloc->width + (canvas->xy[0] =
3730 (int)rint(gtk_adjustment_get_value(canvas->adjustments[0])));
3731 canvas->xy[3] = alloc->height + (canvas->xy[1] =
3732 (int)rint(gtk_adjustment_get_value(canvas->adjustments[1])));
3733 }
3734
wjcanvas_size_allocate(GtkWidget * widget,GtkAllocation * allocation)3735 static void wjcanvas_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
3736 {
3737 wjcanvas *canvas = WJCANVAS(widget);
3738 GtkAllocation alloc0;
3739 int conf;
3740
3741
3742 /* Don't send useless configure events */
3743 gtk_widget_get_allocation(widget, &alloc0);
3744 conf = canvas->resize | (allocation->width ^ alloc0.width) |
3745 (allocation->height ^ alloc0.height);
3746 canvas->resizing = canvas->resize;
3747 canvas->resize = FALSE;
3748
3749 gtk_widget_set_allocation(widget, allocation);
3750
3751 g_object_freeze_notify(G_OBJECT(canvas->adjustments[0]));
3752 g_object_freeze_notify(G_OBJECT(canvas->adjustments[1]));
3753 wjcanvas_readjust(canvas, 0, allocation);
3754 wjcanvas_readjust(canvas, 1, allocation);
3755 wjcanvas_set_extents(canvas, allocation);
3756
3757 if (gtk_widget_get_realized(widget))
3758 {
3759 /* In GTK+3 this'll do whole window redraw for any change */
3760 gdk_window_move_resize(gtk_widget_get_window(widget),
3761 allocation->x, allocation->y,
3762 allocation->width, allocation->height);
3763
3764 /* Replicate behaviour of GtkDrawingCanvas */
3765 if (conf) wjcanvas_send_configure(widget, allocation);
3766 }
3767
3768 g_object_thaw_notify(G_OBJECT(canvas->adjustments[0]));
3769 g_object_thaw_notify(G_OBJECT(canvas->adjustments[1]));
3770 canvas->resizing = FALSE;
3771 }
3772
wjcanvas_get_preferred_width(GtkWidget * widget,gint * min,gint * nat)3773 static void wjcanvas_get_preferred_width(GtkWidget *widget, gint *min, gint *nat)
3774 {
3775 *min = 1;
3776 *nat = WJCANVAS(widget)->size[0];
3777 }
3778
wjcanvas_get_preferred_height(GtkWidget * widget,gint * min,gint * nat)3779 static void wjcanvas_get_preferred_height(GtkWidget *widget, gint *min, gint *nat)
3780 {
3781 *min = 1;
3782 *nat = WJCANVAS(widget)->size[1];
3783 }
3784
3785 #if 0 /* Direct descendants of GtkWidget can let it redirect these two */
3786 static void wjcanvas_get_preferred_width_for_height(GtkWidget *widget, gint h,
3787 gint *min, gint *nat)
3788 {
3789 *min = 1;
3790 *nat = WJCANVAS(widget)->size[0];
3791 }
3792
3793 static void wjcanvas_get_preferred_height_for_width(GtkWidget *widget, gint w,
3794 gint *min, gint *nat)
3795 {
3796 *min = 1;
3797 *nat = WJCANVAS(widget)->size[1];
3798 }
3799 #endif
3800
3801 /* We do scrolling in both directions at once if possible, to avoid ultimately
3802 * useless repaint ops and associated flickers - WJ */
wjcanvas_adjustment_value_changed(GtkAdjustment * adjustment,gpointer data)3803 static void wjcanvas_adjustment_value_changed(GtkAdjustment *adjustment, gpointer data)
3804 {
3805 GtkWidget *widget = data;
3806 wjcanvas *canvas = data;
3807 GtkAllocation alloc;
3808 int oxy[4];
3809
3810 if (!GTK_IS_ADJUSTMENT(adjustment) || !IS_WJCANVAS(data)) return;
3811 gtk_widget_get_allocation(widget, &alloc);
3812 copy4(oxy, canvas->xy);
3813 wjcanvas_set_extents(data, &alloc); // Set new window extents
3814 /* No scrolling in GTK+3, rely on caching */
3815 if (!gtk_widget_get_mapped(widget)) return;
3816 if ((oxy[0] ^ canvas->xy[0]) | (oxy[1] ^ canvas->xy[1])) // If moved
3817 gtk_widget_queue_draw(widget);
3818 }
3819
wjcanvas_drop_adjustment(wjcanvas * canvas,int which)3820 static void wjcanvas_drop_adjustment(wjcanvas *canvas, int which)
3821 {
3822 GtkAdjustment **slot = canvas->adjustments + which;
3823
3824 if (!*slot) return;
3825 g_signal_handlers_disconnect_by_func(*slot,
3826 wjcanvas_adjustment_value_changed, canvas);
3827 g_object_unref(*slot);
3828 *slot = NULL;
3829 }
3830
wjcanvas_prepare_adjustment(wjcanvas * canvas,int which,GtkAdjustment * adjustment)3831 static GtkAdjustment *wjcanvas_prepare_adjustment(wjcanvas *canvas, int which,
3832 GtkAdjustment *adjustment)
3833 {
3834 GtkAdjustment **slot = canvas->adjustments + which;
3835
3836 if (adjustment && (adjustment == *slot)) return (NULL); // Leave alone
3837 if (!adjustment) adjustment = gtk_adjustment_new(0, 0, 0, 0, 0, 0);
3838 wjcanvas_drop_adjustment(canvas, which);
3839 g_object_ref_sink(*slot = adjustment);
3840 g_signal_connect(adjustment, "value-changed",
3841 G_CALLBACK(wjcanvas_adjustment_value_changed), canvas);
3842 return (adjustment);
3843 }
3844
wjcanvas_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)3845 static void wjcanvas_set_property(GObject *object, guint prop_id,
3846 const GValue *value, GParamSpec *pspec)
3847 {
3848 if ((prop_id == P_HADJ) || (prop_id == P_VADJ))
3849 {
3850 wjcanvas *canvas = WJCANVAS(object);
3851 GtkAllocation alloc;
3852 GtkAdjustment *adj;
3853 int which = prop_id - P_HADJ;
3854
3855 gtk_widget_get_allocation(GTK_WIDGET(canvas), &alloc);
3856 adj = wjcanvas_prepare_adjustment(
3857 canvas, which, g_value_get_object(value));
3858 if (adj && !wjcanvas_readjust(canvas, which, &alloc))
3859 wjcanvas_adjustment_value_changed(adj, canvas); // Readjust anyway
3860 }
3861 // !!! React to "*scroll-policy" as an invalid ID if trying to set
3862 else G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
3863 }
3864
wjcanvas_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)3865 static void wjcanvas_get_property(GObject *object, guint prop_id, GValue *value,
3866 GParamSpec *pspec)
3867 {
3868 wjcanvas *canvas = WJCANVAS(object);
3869
3870 if ((prop_id == P_HADJ) || (prop_id == P_VADJ))
3871 g_value_set_object(value, canvas->adjustments[prop_id - P_HADJ]);
3872 else if ((prop_id == P_HSCP) || (prop_id == P_VSCP))
3873 g_value_set_enum(value, GTK_SCROLL_NATURAL);
3874 else G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
3875 }
3876
wjcanvas_drop_cache(wjcanvas * canvas)3877 static void wjcanvas_drop_cache(wjcanvas *canvas)
3878 {
3879 cairo_region_destroy(canvas->r);
3880 canvas->r = NULL;
3881 if (canvas->cache.s)
3882 {
3883 cairo_surface_fdestroy(canvas->cache.s);
3884 canvas->cache.s = NULL;
3885 }
3886 }
3887
wjcanvas_scale_change(GObject * object,GParamSpec * pspec,gpointer user_data)3888 static void wjcanvas_scale_change(GObject *object, GParamSpec *pspec, gpointer user_data)
3889 {
3890 wjcanvas *canvas = WJCANVAS(object);
3891
3892 if (canvas->cache.s && (canvas->cache.scale !=
3893 gtk_widget_get_scale_factor(GTK_WIDGET(canvas))))
3894 wjcanvas_drop_cache(canvas);
3895 }
3896
wjcanvas_unmap(GtkWidget * widget)3897 static void wjcanvas_unmap(GtkWidget *widget)
3898 {
3899 wjcanvas *canvas = WJCANVAS(widget);
3900
3901 GTK_WIDGET_CLASS(wjcanvas_parent_class)->unmap(widget);
3902 wjcanvas_drop_cache(canvas);
3903 }
3904
wjcanvas_destroy(GtkWidget * widget)3905 static void wjcanvas_destroy(GtkWidget *widget)
3906 {
3907 wjcanvas *canvas = WJCANVAS(widget);
3908
3909 wjcanvas_drop_adjustment(canvas, 0);
3910 wjcanvas_drop_adjustment(canvas, 1);
3911 wjcanvas_drop_cache(canvas);
3912 GTK_WIDGET_CLASS(wjcanvas_parent_class)->destroy(widget);
3913 }
3914
wjcanvas_class_init(wjcanvasClass * class)3915 static void wjcanvas_class_init(wjcanvasClass *class)
3916 {
3917 GtkWidgetClass *wclass = GTK_WIDGET_CLASS(class);
3918 GObjectClass *oclass = G_OBJECT_CLASS(class);
3919
3920 oclass->set_property = wjcanvas_set_property;
3921 oclass->get_property = wjcanvas_get_property;
3922 wclass->destroy = wjcanvas_destroy;
3923 wclass->realize = wjcanvas_realize;
3924 wclass->unmap = wjcanvas_unmap;
3925 wclass->draw = wjcanvas_draw;
3926 wclass->size_allocate = wjcanvas_size_allocate;
3927 wclass->get_preferred_width = wjcanvas_get_preferred_width;
3928 wclass->get_preferred_height = wjcanvas_get_preferred_height;
3929 /* Default handlers in GtkWidget redirect these two anyway */
3930 // wclass->get_preferred_width_for_height = wjcanvas_get_preferred_width_for_height;
3931 // wclass->get_preferred_height_for_width = wjcanvas_get_preferred_height_for_width;
3932 /* !!! Do not disturb my circles */
3933 wclass->style_updated = NULL;
3934
3935 g_object_class_override_property(oclass, P_HADJ, "hadjustment");
3936 g_object_class_override_property(oclass, P_VADJ, "vadjustment");
3937 g_object_class_override_property(oclass, P_HSCP, "hscroll-policy");
3938 g_object_class_override_property(oclass, P_VSCP, "vscroll-policy");
3939 }
3940
wjcanvas_init(wjcanvas * canvas)3941 static void wjcanvas_init(wjcanvas *canvas)
3942 {
3943 gtk_widget_set_has_window(GTK_WIDGET(canvas), TRUE);
3944 gtk_widget_set_redraw_on_allocate(GTK_WIDGET(canvas), FALSE);
3945 /* Ensure 1x1 at least */
3946 canvas->size[0] = canvas->size[1] = 1;
3947 /* Install fake adjustments */
3948 canvas->adjustments[0] = canvas->adjustments[1] = NULL;
3949 wjcanvas_prepare_adjustment(canvas, 0, NULL);
3950 wjcanvas_prepare_adjustment(canvas, 1, NULL);
3951 /* No cached things yet */
3952 canvas->r = NULL;
3953 /* And no cache, too */
3954 memset(&canvas->cache, 0, sizeof(canvas->cache));
3955 /* Track scale */
3956 g_signal_connect(canvas, "notify::scale-factor",
3957 G_CALLBACK(wjcanvas_scale_change), NULL);
3958 }
3959
wjcanvas_draw_rgb(GtkWidget * widget,int x,int y,int w,int h,unsigned char * rgb,int step,int fill,int repaint)3960 void wjcanvas_draw_rgb(GtkWidget *widget, int x, int y, int w, int h,
3961 unsigned char *rgb, int step, int fill, int repaint)
3962 {
3963 wjcanvas *canvas;
3964 wcache *cache;
3965 cairo_surface_t *s = NULL;
3966 cairo_t *cr;
3967 cairo_rectangle_int_t re;
3968 int y1, x1, rxy[4];
3969
3970 if (!IS_WJCANVAS(widget)) return;
3971 canvas = WJCANVAS(widget);
3972 cache = &canvas->cache;
3973 if (!cache->s) return; // Not visible yet, draw it later
3974 if (!clip(rxy, x, y, x + w, y + h, cache->xy)) return;
3975
3976 if (rgb) s = cairo_upload_rgb(cache->s, NULL, rgb + (rxy[1] - y) * step +
3977 (rxy[0] - x) * 3, rxy[2] - rxy[0], rxy[3] - rxy[1], step);
3978
3979 /* Find out whether area wraps around on X */
3980 x = (cache->dx + rxy[0] - cache->xy[0]) % cache->w;
3981 w = rxy[2] - rxy[0];
3982 x1 = (x + w) % cache->w;
3983 if (x1 < w) w -= x1;
3984 else x1 = 0;
3985
3986 /* Same thing on Y */
3987 y = (cache->dy + rxy[1] - cache->xy[1]) % cache->h;
3988 h = rxy[3] - rxy[1];
3989 y1 = (y + h) % cache->h;
3990 if (y1 < h) h -= y1;
3991 else y1 = 0;
3992
3993 cr = cairo_create(cache->s);
3994 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
3995 if (!s) cairo_set_rgb(cr, fill); // If no bitmap, fill by color
3996
3997 /* Paint the area into cache, in up to 4 pieces */
3998 if (s) cairo_set_source_surface(cr, s, x, y);
3999 cairo_unfilter(cr);
4000 cairo_rectangle(cr, x, y, w, h);
4001 cairo_fill(cr);
4002 if (y1)
4003 {
4004 if (s)
4005 {
4006 cairo_set_source_surface(cr, s, x, -h);
4007 cairo_unfilter(cr);
4008 }
4009 cairo_rectangle(cr, x, 0, w, y1);
4010 cairo_fill(cr);
4011 }
4012 if (x1)
4013 {
4014 if (s)
4015 {
4016 cairo_set_source_surface(cr, s, -w, y);
4017 cairo_unfilter(cr);
4018 }
4019 cairo_rectangle(cr, 0, y, x1, h);
4020 cairo_fill(cr);
4021 if (y1)
4022 {
4023 if (s)
4024 {
4025 cairo_set_source_surface(cr, s, -w, -h);
4026 cairo_unfilter(cr);
4027 }
4028 cairo_rectangle(cr, 0, 0, x1, y1);
4029 cairo_fill(cr);
4030 }
4031 }
4032 cairo_destroy(cr);
4033
4034 if (s) cairo_surface_fdestroy(s);
4035
4036 /* Remember this part is up to date */
4037 if (!canvas->r) canvas->r = cairo_region_create();
4038 re.width = rxy[2] - (re.x = rxy[0]);
4039 re.height = rxy[3] - (re.y = rxy[1]);
4040 cairo_region_union_rectangle(canvas->r, &re);
4041
4042 /* Invalidate part of window if asked to */
4043 if (repaint)
4044 {
4045 re.x -= canvas->xy[0];
4046 re.y -= canvas->xy[1];
4047 // The two rectangle types documented as identical in GTK+3 docs
4048 gdk_window_invalidate_rect(gtk_widget_get_window(widget),
4049 (GdkRectangle*)&re, FALSE);
4050 }
4051 }
4052
4053 // !!! To be called from CANVAS_REPAINT w/area, & from cmd_repaint() with NULL
wjcanvas_uncache(GtkWidget * widget,int * rxy)4054 void wjcanvas_uncache(GtkWidget *widget, int *rxy)
4055 {
4056 wjcanvas *canvas = WJCANVAS(widget);
4057 if (!canvas->r); // Nothing more to do
4058 else if (!rxy) // Total clear
4059 {
4060 cairo_region_destroy(canvas->r);
4061 canvas->r = NULL;
4062 }
4063 else // Partial clear
4064 {
4065 cairo_rectangle_int_t re = {
4066 rxy[0], rxy[1], rxy[2] - rxy[0], rxy[3] - rxy[1] };
4067 cairo_region_subtract_rectangle(canvas->r, &re);
4068 }
4069 }
4070
wjcanvas_set_expose(GtkWidget * widget,GCallback handler,gpointer user_data)4071 void wjcanvas_set_expose(GtkWidget *widget, GCallback handler, gpointer user_data)
4072 {
4073 wjcanvas *canvas;
4074
4075 if (!IS_WJCANVAS(widget)) return;
4076 canvas = WJCANVAS(widget);
4077 canvas->expose = (wjc_expose_f)handler;
4078 canvas->udata = user_data;
4079 }
4080
wjcanvas_size(GtkWidget * widget,int width,int height)4081 void wjcanvas_size(GtkWidget *widget, int width, int height)
4082 {
4083 wjcanvas *canvas;
4084
4085 if (!IS_WJCANVAS(widget)) return;
4086 canvas = WJCANVAS(widget);
4087 if ((canvas->size[0] == width) && (canvas->size[1] == height)) return;
4088 canvas->size[0] = width;
4089 canvas->size[1] = height;
4090 canvas->resize = TRUE;
4091 /* Forget cached rectangles */
4092 cairo_region_destroy(canvas->r);
4093 canvas->r = NULL;
4094 gtk_widget_queue_resize(widget);
4095 }
4096
wjcanvas_offset(GtkAdjustment * adj,int dv)4097 static int wjcanvas_offset(GtkAdjustment *adj, int dv)
4098 {
4099 double nv, up, v;
4100 int step, dw = dv;
4101
4102 step = (int)(gtk_adjustment_get_step_increment(adj) + 0.5);
4103 if (step)
4104 {
4105 dw = abs(dw) + step - 1;
4106 dw -= dw % step;
4107 if (dv < 0) dw = -dw;
4108 }
4109
4110 up = gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj);
4111 nv = (v = gtk_adjustment_get_value(adj)) + dw;
4112 if (nv > up) nv = up;
4113 if (nv < 0.0) nv = 0.0;
4114 gtk_adjustment_set_value(adj, nv);
4115 return (v != nv);
4116 }
4117
wjcanvas_scroll_in(GtkWidget * widget,int x,int y)4118 int wjcanvas_scroll_in(GtkWidget *widget, int x, int y)
4119 {
4120 wjcanvas *canvas = WJCANVAS(widget);
4121 int dx = 0, dy = 0;
4122
4123 if (!canvas->adjustments[0] || !canvas->adjustments[1]) return (FALSE);
4124
4125 if (x < canvas->xy[0]) dx = (x < 0 ? 0 : x) - canvas->xy[0];
4126 else if (x >= canvas->xy[2]) dx = (x >= canvas->size[0] ?
4127 canvas->size[0] : x + 1) - canvas->xy[2];
4128 if (y < canvas->xy[1]) dy = (y < 0 ? 0 : y) - canvas->xy[1];
4129 else if (y >= canvas->xy[3]) dy = (y >= canvas->size[1] ?
4130 canvas->size[1] : y + 1) - canvas->xy[3];
4131 if (!(dx | dy)) return (FALSE);
4132
4133 g_object_freeze_notify(G_OBJECT(canvas->adjustments[0]));
4134 g_object_freeze_notify(G_OBJECT(canvas->adjustments[1]));
4135
4136 dx = wjcanvas_offset(canvas->adjustments[0], dx);
4137 dy = wjcanvas_offset(canvas->adjustments[1], dy);
4138
4139 g_object_thaw_notify(G_OBJECT(canvas->adjustments[0]));
4140 g_object_thaw_notify(G_OBJECT(canvas->adjustments[1]));
4141 return (dx | dy);
4142 }
4143
4144 #define WJCANVAS_SCROLL_LIMIT 333 /* 3 steps/second */
4145
4146 /* If mouse moved outside canvas, scroll canvas & warp cursor back in */
wjcanvas_bind_mouse(GtkWidget * widget,GdkEventMotion * event,int x,int y)4147 int wjcanvas_bind_mouse(GtkWidget *widget, GdkEventMotion *event, int x, int y)
4148 {
4149 wjcanvas *canvas = WJCANVAS(widget);
4150 int oldv[4];
4151
4152 copy4(oldv, canvas->xy);
4153 x += oldv[0]; y += oldv[1];
4154 if ((x >= oldv[0]) && (x < oldv[2]) && (y >= oldv[1]) && (y < oldv[3]))
4155 return (FALSE);
4156 /* Limit scrolling rate for absolute pointing devices */
4157 if ((gdk_device_get_source(event->device) != GDK_SOURCE_MOUSE) &&
4158 (event->time < canvas->scrolltime + WJCANVAS_SCROLL_LIMIT))
4159 return (FALSE);
4160 if (!wjcanvas_scroll_in(widget, x, y)) return (FALSE);
4161 canvas->scrolltime = event->time;
4162 return (move_mouse_relative(oldv[0] - canvas->xy[0], oldv[1] - canvas->xy[1]));
4163 }
4164
4165 #else /* if GTK_MAJOR_VERSION <= 2 */
4166
4167 #define WJCANVAS(obj) GTK_CHECK_CAST(obj, wjcanvas_get_type(), wjcanvas)
4168 #define IS_WJCANVAS(obj) GTK_CHECK_TYPE(obj, wjcanvas_get_type())
4169
4170 typedef struct
4171 {
4172 GtkWidget widget; // Parent class
4173 GtkAdjustment *adjustments[2];
4174 GdkGC *scroll_gc; // For scrolling in GTK+1
4175 int xy[4]; // Viewport
4176 int size[2]; // Requested (virtual) size
4177 int resize; // Resize was requested
4178 int resizing; // Requested resize is happening
4179 guint32 scrolltime; // For autoscroll rate-limiting
4180 } wjcanvas;
4181
4182 typedef struct
4183 {
4184 GtkWidgetClass parent_class;
4185 void (*set_scroll_adjustments)(wjcanvas *canvas,
4186 GtkAdjustment *hadjustment, GtkAdjustment *vadjustment);
4187 } wjcanvasClass;
4188
4189 static GtkWidgetClass *widget_class;
4190 static GtkType wjcanvas_type;
4191
4192 static GtkType wjcanvas_get_type();
4193
wjcanvas_send_configure(GtkWidget * widget)4194 static void wjcanvas_send_configure(GtkWidget *widget)
4195 {
4196 #if GTK2VERSION >= 2 /* GTK+ 2.2+ */
4197 GdkEvent *event = gdk_event_new(GDK_CONFIGURE);
4198
4199 event->configure.window = g_object_ref(widget->window);
4200 event->configure.send_event = TRUE;
4201 event->configure.x = widget->allocation.x;
4202 event->configure.y = widget->allocation.y;
4203 event->configure.width = widget->allocation.width;
4204 event->configure.height = widget->allocation.height;
4205
4206 gtk_widget_event(widget, event);
4207 gdk_event_free(event);
4208 #else /* GTK+ 1.x or 2.0 */
4209 GdkEventConfigure event;
4210
4211 event.type = GDK_CONFIGURE;
4212 event.window = widget->window;
4213 event.send_event = TRUE;
4214 event.x = widget->allocation.x;
4215 event.y = widget->allocation.y;
4216 event.width = widget->allocation.width;
4217 event.height = widget->allocation.height;
4218
4219 gdk_window_ref(event.window);
4220 gtk_widget_event(widget, (GdkEvent *)&event);
4221 gdk_window_unref(event.window);
4222 #endif
4223 }
4224
wjcanvas_realize(GtkWidget * widget)4225 static void wjcanvas_realize(GtkWidget *widget)
4226 {
4227 GdkWindowAttr attrs;
4228
4229
4230 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
4231
4232 attrs.x = widget->allocation.x;
4233 attrs.y = widget->allocation.y;
4234 attrs.width = widget->allocation.width;
4235 attrs.height = widget->allocation.height;
4236 attrs.window_type = GDK_WINDOW_CHILD;
4237 attrs.wclass = GDK_INPUT_OUTPUT;
4238 attrs.visual = gtk_widget_get_visual(widget);
4239 attrs.colormap = gtk_widget_get_colormap(widget);
4240 // Add the same events as GtkViewport does
4241 attrs.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK |
4242 GDK_BUTTON_PRESS_MASK;
4243 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
4244 &attrs, GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP);
4245 gdk_window_set_user_data(widget->window, widget);
4246
4247 #if GTK_MAJOR_VERSION == 1
4248 fix_gdk_events(widget->window);
4249 WJCANVAS(widget)->scroll_gc = gdk_gc_new(widget->window);
4250 gdk_gc_set_exposures(WJCANVAS(widget)->scroll_gc, TRUE);
4251 #endif
4252
4253 widget->style = gtk_style_attach(widget->style, widget->window);
4254 gdk_window_set_back_pixmap(widget->window, NULL, FALSE);
4255
4256 /* Replicate behaviour of GtkDrawingCanvas */
4257 wjcanvas_send_configure(widget);
4258 }
4259
4260 #if GTK_MAJOR_VERSION == 1
4261
wjcanvas_unrealize(GtkWidget * widget)4262 static void wjcanvas_unrealize(GtkWidget *widget)
4263 {
4264 wjcanvas *canvas = WJCANVAS(widget);
4265
4266 gdk_gc_destroy(canvas->scroll_gc);
4267 canvas->scroll_gc = NULL;
4268
4269 if (widget_class->unrealize) widget_class->unrealize(widget);
4270 }
4271
4272 #endif
4273
wjcanvas_readjust(wjcanvas * canvas,int which)4274 static int wjcanvas_readjust(wjcanvas *canvas, int which)
4275 {
4276 GtkWidget *widget = GTK_WIDGET(canvas);
4277 GtkAdjustment *adj = canvas->adjustments[which];
4278 int sz, wp;
4279 double oldv, newv;
4280
4281 oldv = adj->value;
4282 wp = which ? widget->allocation.height : widget->allocation.width;
4283 sz = canvas->size[which];
4284 if (sz < wp) sz = wp;
4285 adj->page_size = wp;
4286 adj->step_increment = wp * 0.1;
4287 adj->page_increment = wp * 0.9;
4288 adj->lower = 0;
4289 adj->upper = sz;
4290 adj->value = newv = oldv < 0.0 ? 0.0 : oldv > sz - wp ? sz - wp : oldv;
4291
4292 return (newv != oldv);
4293 }
4294
wjcanvas_set_extents(wjcanvas * canvas)4295 static void wjcanvas_set_extents(wjcanvas *canvas)
4296 {
4297 GtkWidget *widget = GTK_WIDGET(canvas);
4298
4299 canvas->xy[2] = (canvas->xy[0] = ADJ2INT(canvas->adjustments[0])) +
4300 widget->allocation.width;
4301 canvas->xy[3] = (canvas->xy[1] = ADJ2INT(canvas->adjustments[1])) +
4302 widget->allocation.height;
4303 }
4304
4305 #if GTK_MAJOR_VERSION == 1
4306
wjcanvas_send_expose(GtkWidget * widget,int x,int y,int w,int h)4307 static void wjcanvas_send_expose(GtkWidget *widget, int x, int y, int w, int h)
4308 {
4309 GdkEventExpose event;
4310
4311 event.type = GDK_EXPOSE;
4312 event.send_event = TRUE;
4313 event.window = widget->window;
4314 event.area.x = x;
4315 event.area.y = y;
4316 event.area.width = w;
4317 event.area.height = h;
4318 event.count = 0;
4319
4320 gdk_window_ref(event.window);
4321 gtk_widget_event(widget, (GdkEvent *)&event);
4322 gdk_window_unref(event.window);
4323 }
4324
4325 #endif
4326
wjcanvas_scroll(GtkWidget * widget,int * oxy)4327 static void wjcanvas_scroll(GtkWidget *widget, int *oxy)
4328 {
4329 wjcanvas *canvas = WJCANVAS(widget);
4330 int nxy[4], rxy[4], fulldraw;
4331
4332
4333 if (!GTK_WIDGET_DRAWABLE(widget)) return; // No use
4334 if (canvas->resizing) return; // No reason
4335 copy4(nxy, canvas->xy);
4336 if (!((oxy[0] ^ nxy[0]) | (oxy[1] ^ nxy[1]))) return; // No scrolling
4337
4338 /* Scroll or redraw? */
4339 fulldraw = !clip(rxy, nxy[0], nxy[1], nxy[2], nxy[3], oxy);
4340 #if GTK_MAJOR_VERSION == 1
4341 /* Check for resize - GTK+1 operates with ForgetGravity */
4342 if ((oxy[2] - oxy[0] - nxy[2] + nxy[0]) | (oxy[3] - oxy[1] - nxy[3] + nxy[1]))
4343 return; // A full expose event should be queued anyway
4344 /* Check for in-flight draw ops */
4345 if (GTK_WIDGET_FULLDRAW_PENDING(widget)) return;
4346 fulldraw |= GTK_WIDGET_REDRAW_PENDING(widget);
4347 // Updating draws in queue might be possible, but sure is insanely hard
4348 #endif
4349
4350 if (fulldraw) gtk_widget_queue_draw(widget); // Just redraw everything
4351 else // Scroll
4352 {
4353 #if GTK_MAJOR_VERSION == 1
4354 GdkEvent *event;
4355
4356 gdk_window_copy_area(widget->window, canvas->scroll_gc,
4357 rxy[0] - nxy[0], rxy[1] - nxy[1],
4358 widget->window,
4359 rxy[0] - oxy[0], rxy[1] - oxy[1],
4360 rxy[2] - rxy[0], rxy[3] - rxy[1]);
4361
4362 /* Have to process GraphicsExpose events before next scrolling */
4363 while ((event = gdk_event_get_graphics_expose(widget->window)))
4364 {
4365 gtk_widget_event(widget, event);
4366 if (event->expose.count == 0)
4367 {
4368 gdk_event_free(event);
4369 break;
4370 }
4371 gdk_event_free(event);
4372 }
4373
4374 /* Now draw the freed-up part(s) */
4375 if (rxy[1] > nxy[1]) wjcanvas_send_expose(widget,
4376 0, 0, nxy[2] - nxy[0], rxy[1] - nxy[1]);
4377 if (rxy[3] < nxy[3]) wjcanvas_send_expose(widget,
4378 0, rxy[3] - nxy[1], nxy[2] - nxy[0], nxy[3] - rxy[3]);
4379 if (rxy[0] > nxy[0]) wjcanvas_send_expose(widget,
4380 0, rxy[1] - nxy[1], rxy[0] - nxy[0], rxy[3] - rxy[1]);
4381 if (rxy[2] < nxy[2]) wjcanvas_send_expose(widget,
4382 rxy[2] - nxy[0], rxy[1] - nxy[1], nxy[2] - rxy[2], rxy[3] - rxy[1]);
4383 #elif defined GDK_WINDOWING_WIN32
4384 /* !!! On Windows, gdk_window_scroll() had been badly broken in
4385 * GTK+ 2.6.5, then fixed in 2.8.10, but with changed behaviour
4386 * with regard to screen updates.
4387 * Unlike all that, my own window scrolling code here behaves
4388 * consistently :-) - WJ */
4389 GdkRegion *tmp;
4390 int dx = oxy[0] - nxy[0], dy = oxy[1] - nxy[1];
4391
4392 /* Adjust the pending update region, let system add the rest
4393 * through WM_PAINT (but, in Wine WM_PAINTs can lag behind) */
4394 if ((tmp = gdk_window_get_update_area(widget->window)))
4395 {
4396 gdk_region_offset(tmp, dx, dy);
4397 gdk_window_invalidate_region(widget->window, tmp, FALSE);
4398 gdk_region_destroy(tmp);
4399 }
4400
4401 /* Tell Windows to scroll window AND send us invalidations */
4402 ScrollWindowEx(GDK_WINDOW_HWND(widget->window), dx, dy,
4403 NULL, NULL, NULL, NULL, SW_INVALIDATE);
4404
4405 /* Catch the invalidations; this cures Wine, while on Windows
4406 * just improves render consistency a bit.
4407 * And needs by-region expose to not waste a lot of work - WJ */
4408 while (TRUE)
4409 {
4410 GdkEvent *event = gdk_event_get_graphics_expose(widget->window);
4411 if (!event) break;
4412 /* !!! The above function is buggy in GTK+2/Win32: it
4413 * doesn't ref the window, so we must do it here - WJ */
4414 gdk_window_ref(widget->window);
4415 gdk_window_invalidate_region(widget->window,
4416 event->expose.region, FALSE);
4417 gdk_event_free(event);
4418 }
4419
4420 /* And tell GTK+ to redraw it */
4421 gdk_window_process_updates(widget->window, FALSE);
4422 #else
4423 gdk_window_scroll(widget->window,
4424 oxy[0] - nxy[0], oxy[1] - nxy[1]);
4425 #endif
4426 }
4427
4428 /* !!! _Maybe_ we need gdk_window_process_updates(widget->window, FALSE) here
4429 * in GTK+2 - but then, maybe we don't; only practice will tell.
4430 * Rule of thumb is, you need atrociously slow render to make forced updates
4431 * useful - otherwise, they just create a slowdown. - WJ */
4432
4433 }
4434
wjcanvas_size_allocate(GtkWidget * widget,GtkAllocation * allocation)4435 static void wjcanvas_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
4436 {
4437 wjcanvas *canvas = WJCANVAS(widget);
4438 int hchg, vchg, conf, oxy[4];
4439
4440
4441 /* Don't send useless configure events */
4442 conf = canvas->resize | (allocation->width ^ widget->allocation.width) |
4443 (allocation->height ^ widget->allocation.height);
4444 canvas->resizing = canvas->resize;
4445 canvas->resize = FALSE;
4446
4447 copy4(oxy, canvas->xy);
4448 widget->allocation = *allocation;
4449 hchg = wjcanvas_readjust(canvas, 0);
4450 vchg = wjcanvas_readjust(canvas, 1);
4451 wjcanvas_set_extents(canvas);
4452
4453 if (GTK_WIDGET_REALIZED(widget))
4454 {
4455 gdk_window_move_resize(widget->window,
4456 allocation->x, allocation->y,
4457 allocation->width, allocation->height);
4458 wjcanvas_scroll(widget, oxy);
4459
4460 /* Replicate behaviour of GtkDrawingCanvas */
4461 if (conf) wjcanvas_send_configure(widget);
4462 }
4463
4464 gtk_adjustment_changed(canvas->adjustments[0]);
4465 gtk_adjustment_changed(canvas->adjustments[1]);
4466 if (hchg) gtk_adjustment_value_changed(canvas->adjustments[0]);
4467 if (vchg) gtk_adjustment_value_changed(canvas->adjustments[1]);
4468 canvas->resizing = FALSE;
4469 }
4470
4471 /* We do scrolling in both directions at once if possible, to avoid ultimately
4472 * useless repaint ops and associated flickers - WJ */
wjcanvas_adjustment_value_changed(GtkAdjustment * adjustment,gpointer data)4473 static void wjcanvas_adjustment_value_changed(GtkAdjustment *adjustment, gpointer data)
4474 {
4475 GtkWidget *widget = data;
4476 wjcanvas *canvas;
4477 int oxy[4];
4478
4479
4480 if (!GTK_IS_ADJUSTMENT(adjustment) || !IS_WJCANVAS(data)) return;
4481 canvas = WJCANVAS(data);
4482
4483 copy4(oxy, canvas->xy);
4484 wjcanvas_set_extents(canvas); // Set new window extents
4485 wjcanvas_scroll(widget, oxy);
4486 }
4487
wjcanvas_drop_adjustment(wjcanvas * canvas,int which)4488 static void wjcanvas_drop_adjustment(wjcanvas *canvas, int which)
4489 {
4490 GtkAdjustment **slot = canvas->adjustments + which;
4491
4492 if (*slot)
4493 {
4494 GtkObject *adj = GTK_OBJECT(*slot);
4495 gtk_signal_disconnect_by_func(adj,
4496 GTK_SIGNAL_FUNC(wjcanvas_adjustment_value_changed), canvas);
4497 gtk_object_unref(adj);
4498 *slot = NULL;
4499 }
4500 }
4501
wjcanvas_prepare_adjustment(wjcanvas * canvas,GtkAdjustment * adjustment)4502 static GtkAdjustment *wjcanvas_prepare_adjustment(wjcanvas *canvas,
4503 GtkAdjustment *adjustment)
4504 {
4505 GtkObject *adj;
4506
4507 if (!adjustment)
4508 adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 0, 0, 0));
4509 adj = GTK_OBJECT(adjustment);
4510 gtk_object_ref(adj);
4511 gtk_object_sink(adj);
4512 gtk_signal_connect(adj, "value_changed",
4513 GTK_SIGNAL_FUNC(wjcanvas_adjustment_value_changed), (gpointer)canvas);
4514 return (adjustment);
4515 }
4516
wjcanvas_set_adjustment(wjcanvas * canvas,int which,GtkAdjustment * adjustment)4517 static void wjcanvas_set_adjustment(wjcanvas *canvas, int which,
4518 GtkAdjustment *adjustment)
4519 {
4520 int changed;
4521
4522 wjcanvas_drop_adjustment(canvas, which);
4523 adjustment = wjcanvas_prepare_adjustment(canvas, adjustment);
4524 canvas->adjustments[which] = adjustment;
4525
4526 changed = wjcanvas_readjust(canvas, which);
4527 gtk_adjustment_changed(adjustment);
4528 if (changed) gtk_adjustment_value_changed(adjustment);
4529 else wjcanvas_adjustment_value_changed(adjustment, canvas); // Readjust anyway
4530 }
4531
wjcanvas_set_scroll_adjustments(wjcanvas * canvas,GtkAdjustment * hadjustment,GtkAdjustment * vadjustment)4532 static void wjcanvas_set_scroll_adjustments(wjcanvas *canvas,
4533 GtkAdjustment *hadjustment, GtkAdjustment *vadjustment)
4534 {
4535 if (canvas->adjustments[0] != hadjustment)
4536 wjcanvas_set_adjustment(canvas, 0, hadjustment);
4537 if (canvas->adjustments[1] != vadjustment)
4538 wjcanvas_set_adjustment(canvas, 1, vadjustment);
4539 }
4540
wjcanvas_destroy(GtkObject * object)4541 static void wjcanvas_destroy(GtkObject *object)
4542 {
4543 wjcanvas *canvas = WJCANVAS(object);
4544
4545 wjcanvas_drop_adjustment(canvas, 0);
4546 wjcanvas_drop_adjustment(canvas, 1);
4547 GTK_OBJECT_CLASS(widget_class)->destroy(object);
4548 }
4549
wjcanvas_class_init(wjcanvasClass * class)4550 static void wjcanvas_class_init(wjcanvasClass *class)
4551 {
4552 GtkWidgetClass *wclass = GTK_WIDGET_CLASS(class);
4553
4554
4555 widget_class = gtk_type_class(GTK_TYPE_WIDGET);
4556 GTK_OBJECT_CLASS(class)->destroy = wjcanvas_destroy;
4557 wclass->realize = wjcanvas_realize;
4558 #if GTK_MAJOR_VERSION == 1
4559 wclass->unrealize = wjcanvas_unrealize;
4560 #endif
4561 wclass->size_allocate = wjcanvas_size_allocate;
4562 // !!! Default "style_set" handler can reenable background clear
4563 wclass->style_set = NULL;
4564 class->set_scroll_adjustments = wjcanvas_set_scroll_adjustments;
4565
4566 wclass->set_scroll_adjustments_signal = gtk_signal_new(
4567 "set_scroll_adjustments", GTK_RUN_LAST, wjcanvas_type,
4568 GTK_SIGNAL_OFFSET(wjcanvasClass, set_scroll_adjustments),
4569 gtk_marshal_NONE__POINTER_POINTER, GTK_TYPE_NONE,
4570 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
4571 }
4572
wjcanvas_init(wjcanvas * canvas)4573 static void wjcanvas_init(wjcanvas *canvas)
4574 {
4575 GTK_WIDGET_UNSET_FLAGS(canvas, GTK_NO_WINDOW);
4576 #if GTK_MAJOR_VERSION == 2
4577 GTK_WIDGET_UNSET_FLAGS(canvas, GTK_DOUBLE_BUFFERED);
4578 gtk_widget_set_redraw_on_allocate(GTK_WIDGET(canvas), FALSE);
4579 #endif
4580 /* Install fake adjustments */
4581 canvas->adjustments[0] = wjcanvas_prepare_adjustment(canvas, NULL);
4582 canvas->adjustments[1] = wjcanvas_prepare_adjustment(canvas, NULL);
4583 }
4584
wjcanvas_get_type()4585 static GtkType wjcanvas_get_type()
4586 {
4587 if (!wjcanvas_type)
4588 {
4589 static const GtkTypeInfo wjcanvas_info = {
4590 "wjCanvas", sizeof(wjcanvas), sizeof(wjcanvasClass),
4591 (GtkClassInitFunc)wjcanvas_class_init,
4592 (GtkObjectInitFunc)wjcanvas_init,
4593 NULL, NULL, NULL };
4594 wjcanvas_type = gtk_type_unique(GTK_TYPE_WIDGET, &wjcanvas_info);
4595 }
4596
4597 return (wjcanvas_type);
4598 }
4599
wjcanvas_set_expose(GtkWidget * widget,GtkSignalFunc handler,gpointer user_data)4600 void wjcanvas_set_expose(GtkWidget *widget, GtkSignalFunc handler, gpointer user_data)
4601 {
4602 gtk_signal_connect(GTK_OBJECT(widget), "expose_event", handler, user_data);
4603 }
4604
wjcanvas_size(GtkWidget * widget,int width,int height)4605 void wjcanvas_size(GtkWidget *widget, int width, int height)
4606 {
4607 wjcanvas *canvas;
4608
4609 if (!IS_WJCANVAS(widget)) return;
4610 canvas = WJCANVAS(widget);
4611 if ((canvas->size[0] == width) && (canvas->size[1] == height)) return;
4612 canvas->size[0] = width;
4613 canvas->size[1] = height;
4614 canvas->resize = TRUE;
4615 #if GTK_MAJOR_VERSION == 1
4616 /* !!! The fields are limited to 16-bit signed, and the values aren't */
4617 widget->requisition.width = MIN(width, 32767);
4618 widget->requisition.height = MIN(height, 32767);
4619 #else /* if GTK_MAJOR_VERSION == 2 */
4620 widget->requisition.width = width;
4621 widget->requisition.height = height;
4622 #endif
4623 gtk_widget_queue_resize(widget);
4624 }
4625
wjcanvas_offset(GtkAdjustment * adj,int dv)4626 static int wjcanvas_offset(GtkAdjustment *adj, int dv)
4627 {
4628 double nv, up;
4629 int step, dw = dv;
4630
4631 step = (int)(adj->step_increment + 0.5);
4632 if (step)
4633 {
4634 dw = abs(dw) + step - 1;
4635 dw -= dw % step;
4636 if (dv < 0) dw = -dw;
4637 }
4638
4639 up = adj->upper - adj->page_size;
4640 nv = adj->value + dw;
4641 if (nv > up) nv = up;
4642 if (nv < 0.0) nv = 0.0;
4643 up = adj->value;
4644 adj->value = nv;
4645 return (up != nv);
4646 }
4647
wjcanvas_scroll_in(GtkWidget * widget,int x,int y)4648 int wjcanvas_scroll_in(GtkWidget *widget, int x, int y)
4649 {
4650 wjcanvas *canvas = WJCANVAS(widget);
4651 int dx = 0, dy = 0;
4652
4653 if (!canvas->adjustments[0] || !canvas->adjustments[1]) return (FALSE);
4654
4655 if (x < canvas->xy[0]) dx = (x < 0 ? 0 : x) - canvas->xy[0];
4656 else if (x >= canvas->xy[2]) dx = (x >= canvas->size[0] ?
4657 canvas->size[0] : x + 1) - canvas->xy[2];
4658 if (y < canvas->xy[1]) dy = (y < 0 ? 0 : y) - canvas->xy[1];
4659 else if (y >= canvas->xy[3]) dy = (y >= canvas->size[1] ?
4660 canvas->size[1] : y + 1) - canvas->xy[3];
4661 if (!(dx | dy)) return (FALSE);
4662
4663 dx = wjcanvas_offset(canvas->adjustments[0], dx);
4664 dy = wjcanvas_offset(canvas->adjustments[1], dy);
4665 if (dx) gtk_adjustment_value_changed(canvas->adjustments[0]);
4666 if (dy) gtk_adjustment_value_changed(canvas->adjustments[1]);
4667 return (dx | dy);
4668 }
4669
4670 #define WJCANVAS_SCROLL_LIMIT 333 /* 3 steps/second */
4671
4672 /* If mouse moved outside canvas, scroll canvas & warp cursor back in */
wjcanvas_bind_mouse(GtkWidget * widget,GdkEventMotion * event,int x,int y)4673 int wjcanvas_bind_mouse(GtkWidget *widget, GdkEventMotion *event, int x, int y)
4674 {
4675 wjcanvas *canvas = WJCANVAS(widget);
4676 int oldv[4];
4677
4678 copy4(oldv, canvas->xy);
4679 x += oldv[0]; y += oldv[1];
4680 if ((x >= oldv[0]) && (x < oldv[2]) && (y >= oldv[1]) && (y < oldv[3]))
4681 return (FALSE);
4682 /* Limit scrolling rate for absolute pointing devices */
4683 #if GTK_MAJOR_VERSION == 1
4684 if ((event->source != GDK_SOURCE_MOUSE) &&
4685 #else /* if GTK_MAJOR_VERSION == 2 */
4686 if ((event->device->source != GDK_SOURCE_MOUSE) &&
4687 #endif
4688 (event->time < canvas->scrolltime + WJCANVAS_SCROLL_LIMIT))
4689 return (FALSE);
4690 if (!wjcanvas_scroll_in(widget, x, y)) return (FALSE);
4691 canvas->scrolltime = event->time;
4692 return (move_mouse_relative(oldv[0] - canvas->xy[0], oldv[1] - canvas->xy[1]));
4693 }
4694
4695 #endif /* GTK+1&2 */
4696
wjcanvas_new()4697 GtkWidget *wjcanvas_new()
4698 {
4699 return (gtk_widget_new(wjcanvas_get_type(), NULL));
4700 }
4701
wjcanvas_get_vport(GtkWidget * widget,int * vport)4702 void wjcanvas_get_vport(GtkWidget *widget, int *vport)
4703 {
4704 copy4(vport, WJCANVAS(widget)->xy);
4705 }
4706
4707 #if GTK_MAJOR_VERSION == 3
4708
4709 // Focusable pixmap widget (on Cairo surfaces, but whatever)
4710
4711 #define WJPIXMAP(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, wjpixmap_get_type(), wjpixmap)
4712 #define IS_WJPIXMAP(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, wjpixmap_get_type())
4713
4714 typedef struct
4715 {
4716 GtkWidget widget; // Parent class
4717 GdkWindow *pixwindow;
4718 cairo_surface_t *pixmap;
4719 cairo_surface_t *cursor;
4720 GdkRectangle pm, cr; // pm is allocation relative, unlike in GTK+1&2
4721 int width, height; // Requested pixmap size
4722 int xc, yc;
4723 int focused_cursor;
4724 } wjpixmap;
4725
4726 typedef struct
4727 {
4728 GtkWidgetClass parent_class;
4729 } wjpixmapClass;
4730
G_DEFINE_TYPE(wjpixmap,wjpixmap,GTK_TYPE_WIDGET)4731 G_DEFINE_TYPE(wjpixmap, wjpixmap, GTK_TYPE_WIDGET)
4732
4733 #define WJPIXMAP_FRAME 1 /* Line width, limited only by common sense */
4734
4735 static gboolean wjpixmap_draw(GtkWidget *widget, cairo_t *cr)
4736 {
4737 wjpixmap *pix = WJPIXMAP(widget);
4738 GtkStyleContext *ctx = gtk_widget_get_style_context(widget);
4739 GtkAllocation alloc;
4740
4741 cairo_save(cr);
4742 gtk_widget_get_allocation(widget, &alloc);
4743 /* Outer window */
4744 if (gtk_cairo_should_draw_window(cr, gtk_widget_get_window(widget)))
4745 {
4746 gtk_render_background(ctx, cr, 0, 0, alloc.width, alloc.height);
4747 // !!! Maybe render pixmap frame here: _before_ focus, as in GTK+1/2?
4748 /* Focus frame */
4749 if (gtk_widget_has_visible_focus(widget))
4750 {
4751 /* !!! Is inside-the-border the right place? */
4752 GtkBorder border;
4753 get_padding_and_border(ctx, NULL, &border, NULL);
4754 gtk_render_focus(ctx, cr, border.left, border.top,
4755 alloc.width - border.left - border.right,
4756 alloc.height - border.top - border.bottom);
4757 }
4758 /* Pixmap frame */
4759 cairo_set_line_width(cr, WJPIXMAP_FRAME);
4760 cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
4761 cairo_set_source_rgb(cr, 0, 0, 0); // Black
4762 cairo_rectangle(cr,
4763 pix->pm.x - WJPIXMAP_FRAME * 0.5,
4764 pix->pm.y - WJPIXMAP_FRAME * 0.5,
4765 pix->pm.width + WJPIXMAP_FRAME,
4766 pix->pm.height + WJPIXMAP_FRAME);
4767 cairo_stroke(cr);
4768 }
4769 /* Pixmap window */
4770 while (gtk_cairo_should_draw_window(cr, pix->pixwindow))
4771 {
4772 /* This widget may get moved to differently-scaled monitor while
4773 * retaining the buffer surfaces, so cairo_unfilter() to at least
4774 * prevent blurring if that happens */
4775 gtk_cairo_transform_to_window(cr, widget, pix->pixwindow);
4776 cairo_set_source_surface(cr, pix->pixmap, 0, 0);
4777 cairo_unfilter(cr);
4778 cairo_rectangle(cr, 0, 0, pix->pm.width, pix->pm.height);
4779 // cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
4780 cairo_fill(cr); // Hope Cairo won't waste much time on clipped-out parts
4781 /* Cursor pixmap */
4782 if (!pix->cursor) break;
4783 if (pix->focused_cursor && !gtk_widget_has_focus(widget)) break;
4784 cairo_set_source_surface(cr, pix->cursor, pix->cr.x, pix->cr.y);
4785 cairo_unfilter(cr);
4786 // cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
4787 cairo_rectangle(cr, pix->cr.x, pix->cr.y, pix->cr.width, pix->cr.height);
4788 cairo_fill(cr); // Hope Cairo won't waste time if outside of clip
4789 break;
4790 }
4791 cairo_restore(cr);
4792
4793 return (FALSE);
4794 }
4795
wjpixmap_position(wjpixmap * pix,GtkAllocation * alloc)4796 static void wjpixmap_position(wjpixmap *pix, GtkAllocation *alloc)
4797 {
4798 int x, y, w, h;
4799 GtkBorder border;
4800
4801 get_padding_and_border(gtk_widget_get_style_context(GTK_WIDGET(pix)),
4802 NULL, NULL, &border);
4803 x = alloc->width - border.left - border.right;
4804 pix->pm.width = w = pix->width <= x ? pix->width : x;
4805 pix->pm.x = (x - w) / 2 + border.left;
4806 y = alloc->height - border.top - border.bottom;
4807 pix->pm.height = h = pix->height <= y ? pix->height : y;
4808 pix->pm.y = (y - h) / 2 + border.top;
4809 }
4810
wjpixmap_realize(GtkWidget * widget)4811 static void wjpixmap_realize(GtkWidget *widget)
4812 {
4813 wjpixmap *pix = WJPIXMAP(widget);
4814 GdkWindow *win;
4815 GdkWindowAttr attrs;
4816 GtkAllocation alloc;
4817
4818 gtk_widget_set_realized(widget, TRUE);
4819 gtk_widget_get_allocation(widget, &alloc);
4820
4821 win = gtk_widget_get_parent_window(widget);
4822 gtk_widget_set_window(widget, win);
4823 g_object_ref(win);
4824
4825 wjpixmap_position(pix, &alloc);
4826
4827 attrs.x = pix->pm.x + alloc.x;
4828 attrs.y = pix->pm.y + alloc.y;
4829 attrs.width = pix->pm.width;
4830 attrs.height = pix->pm.height;
4831 attrs.window_type = GDK_WINDOW_CHILD;
4832 attrs.wclass = GDK_INPUT_OUTPUT;
4833 attrs.visual = gtk_widget_get_visual(widget);
4834 // Add the same events as GtkEventBox does
4835 attrs.event_mask = gtk_widget_get_events(widget) | GDK_BUTTON_MOTION_MASK |
4836 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
4837 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
4838 /* !!! GDK_EXPOSURE_MASK seems really not be needed (as advertised),
4839 * despite GtkDrawingArea still having it */
4840 pix->pixwindow = win = gdk_window_new(win, &attrs,
4841 GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);
4842 /* !!! Versions without this (below 3.12) are useless */
4843 gdk_window_set_event_compression(win, FALSE);
4844
4845 gtk_widget_register_window(widget, win);
4846 }
4847
wjpixmap_unrealize(GtkWidget * widget)4848 static void wjpixmap_unrealize(GtkWidget *widget)
4849 {
4850 wjpixmap *pix = WJPIXMAP(widget);
4851
4852 if (pix->pixwindow)
4853 {
4854 gtk_widget_unregister_window(widget, pix->pixwindow);
4855 gdk_window_destroy(pix->pixwindow);
4856 pix->pixwindow = NULL;
4857 }
4858
4859 GTK_WIDGET_CLASS(wjpixmap_parent_class)->unrealize(widget);
4860 }
4861
wjpixmap_map(GtkWidget * widget)4862 static void wjpixmap_map(GtkWidget *widget)
4863 {
4864 wjpixmap *pix = WJPIXMAP(widget);
4865
4866 GTK_WIDGET_CLASS(wjpixmap_parent_class)->map(widget);
4867 if (pix->pixwindow) gdk_window_show(pix->pixwindow);
4868 }
4869
wjpixmap_unmap(GtkWidget * widget)4870 static void wjpixmap_unmap(GtkWidget *widget)
4871 {
4872 wjpixmap *pix = WJPIXMAP(widget);
4873
4874 if (pix->pixwindow) gdk_window_hide(pix->pixwindow);
4875 GTK_WIDGET_CLASS(wjpixmap_parent_class)->unmap(widget);
4876 }
4877
wjpixmap_size_allocate(GtkWidget * widget,GtkAllocation * allocation)4878 static void wjpixmap_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
4879 {
4880 wjpixmap *pix = WJPIXMAP(widget);
4881
4882 gtk_widget_set_allocation(widget, allocation);
4883 wjpixmap_position(pix, allocation);
4884
4885 if (gtk_widget_get_realized(widget)) gdk_window_move_resize(pix->pixwindow,
4886 pix->pm.x + allocation->x, pix->pm.y + allocation->y,
4887 pix->pm.width, pix->pm.height);
4888 }
4889
wjpixmap_get_size(GtkWidget * widget,gint vert,gint * min,gint * nat)4890 static void wjpixmap_get_size(GtkWidget *widget, gint vert, gint *min, gint *nat)
4891 {
4892 wjpixmap *pix = WJPIXMAP(widget);
4893 GtkBorder border;
4894
4895 get_padding_and_border(gtk_widget_get_style_context(widget), NULL, NULL, &border);
4896
4897 *min = *nat = (vert ? border.top + border.bottom + pix->height :
4898 border.left + border.right + pix->width) + WJPIXMAP_FRAME * 2;
4899 }
4900
wjpixmap_get_preferred_width(GtkWidget * widget,gint * min,gint * nat)4901 static void wjpixmap_get_preferred_width(GtkWidget *widget, gint *min, gint *nat)
4902 {
4903 wjpixmap_get_size(widget, FALSE, min, nat);
4904 }
4905
wjpixmap_get_preferred_height(GtkWidget * widget,gint * min,gint * nat)4906 static void wjpixmap_get_preferred_height(GtkWidget *widget, gint *min, gint *nat)
4907 {
4908 wjpixmap_get_size(widget, TRUE, min, nat);
4909 }
4910
wjpixmap_destroy(GtkWidget * widget)4911 static void wjpixmap_destroy(GtkWidget *widget)
4912 {
4913 wjpixmap *pix = WJPIXMAP(widget);
4914
4915 if (pix->pixmap)
4916 {
4917 cairo_surface_fdestroy(pix->pixmap);
4918 pix->pixmap = NULL;
4919 }
4920 if (pix->cursor)
4921 {
4922 cairo_surface_fdestroy(pix->cursor);
4923 pix->cursor = NULL;
4924 }
4925 GTK_WIDGET_CLASS(wjpixmap_parent_class)->destroy(widget);
4926 }
4927
wjpixmap_class_init(wjpixmapClass * class)4928 static void wjpixmap_class_init(wjpixmapClass *class)
4929 {
4930 GtkWidgetClass *wclass = GTK_WIDGET_CLASS(class);
4931
4932 // wclass->screen_changed = wjpixmap_screen_changed; // as proxy for changing visual?
4933 wclass->destroy = wjpixmap_destroy;
4934 wclass->realize = wjpixmap_realize;
4935 wclass->unrealize = wjpixmap_unrealize;
4936 wclass->map = wjpixmap_map;
4937 wclass->unmap = wjpixmap_unmap;
4938 wclass->draw = wjpixmap_draw;
4939 wclass->size_allocate = wjpixmap_size_allocate;
4940 wclass->get_preferred_width = wjpixmap_get_preferred_width;
4941 wclass->get_preferred_height = wjpixmap_get_preferred_height;
4942 /* Default handlers in GtkWidget redirect these two anyway */
4943 // wclass->get_preferred_width_for_height = wjpixmap_get_preferred_width_for_height;
4944 // wclass->get_preferred_height_for_width = wjpixmap_get_preferred_height_for_width;
4945 /* !!! Do not disturb my circles */
4946 // wclass->style_updated = NULL;
4947 }
4948
wjpixmap_init(wjpixmap * pix)4949 static void wjpixmap_init(wjpixmap *pix)
4950 {
4951 gtk_widget_set_has_window(GTK_WIDGET(pix), FALSE);
4952 pix->pixwindow = NULL;
4953 pix->pixmap = pix->cursor = NULL;
4954 add_css_class(GTK_WIDGET(pix), "wjpixmap");
4955 }
4956
wjpixmap_new(int width,int height)4957 GtkWidget *wjpixmap_new(int width, int height)
4958 {
4959 GtkWidget *widget = gtk_widget_new(wjpixmap_get_type(), NULL);
4960 wjpixmap *pix = WJPIXMAP(widget);
4961
4962 gtk_widget_set_can_focus(widget, TRUE);
4963 pix->width = width; pix->height = height;
4964 return (widget);
4965 }
4966
4967 /* Must be called first to init, and afterwards to access pixmap */
wjpixmap_pixmap(GtkWidget * widget)4968 cairo_surface_t *wjpixmap_pixmap(GtkWidget *widget)
4969 {
4970 wjpixmap *pix = WJPIXMAP(widget);
4971
4972 if (!pix->pixmap)
4973 {
4974 GdkWindow *win = pix->pixwindow;
4975 if (!win) win = gdk_screen_get_root_window(gtk_widget_get_screen(widget));
4976 pix->pixmap = gdk_window_create_similar_surface(win,
4977 CAIRO_CONTENT_COLOR, pix->width, pix->height);
4978 }
4979 return (pix->pixmap);
4980 }
4981
wjpixmap_draw_rgb(GtkWidget * widget,int x,int y,int w,int h,unsigned char * rgb,int step)4982 void wjpixmap_draw_rgb(GtkWidget *widget, int x, int y, int w, int h,
4983 unsigned char *rgb, int step)
4984 {
4985 wjpixmap *pix = WJPIXMAP(widget);
4986 cairo_surface_t *s;
4987 cairo_t *cr;
4988
4989 if (!pix->pixmap) return;
4990 s = cairo_upload_rgb(pix->pixmap, NULL, rgb, w, h, step);
4991
4992 cr = cairo_create(pix->pixmap);
4993 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
4994 cairo_set_source_surface(cr, s, x, y);
4995 cairo_unfilter(cr);
4996 cairo_rectangle(cr, x, y, w, h);
4997 cairo_fill(cr);
4998 cairo_destroy(cr);
4999
5000 cairo_surface_fdestroy(s);
5001 if (pix->pixwindow)
5002 {
5003 GdkRectangle wr = { x, y, w, h };
5004 gdk_window_invalidate_rect(pix->pixwindow, &wr, FALSE);
5005 }
5006 }
5007
wjpixmap_move_cursor(GtkWidget * widget,int x,int y)5008 void wjpixmap_move_cursor(GtkWidget *widget, int x, int y)
5009 {
5010 wjpixmap *pix = WJPIXMAP(widget);
5011 GdkRectangle pm, ocr, tcr1, tcr2, *rcr = NULL;
5012 int dx = x - pix->xc, dy = y - pix->yc;
5013
5014 if (!(dx | dy)) return;
5015 ocr = pix->cr;
5016 pix->cr.x += dx; pix->cr.y += dy;
5017 pix->xc = x; pix->yc = y;
5018
5019 if (!pix->pixmap || !pix->cursor) return;
5020 if (pix->focused_cursor && !gtk_widget_has_focus(widget)) return;
5021
5022 /* Anything visible? */
5023 if (!gtk_widget_get_visible(widget)) return;
5024 pm = pix->pm; pm.x = pm.y = 0;
5025 if (gdk_rectangle_intersect(&ocr, &pm, &tcr1)) rcr = &tcr1;
5026 if (gdk_rectangle_intersect(&pix->cr, &pm, &tcr2))
5027 {
5028 if (rcr) gdk_rectangle_union(&tcr1, &tcr2, rcr = &ocr);
5029 else rcr = &tcr2;
5030 }
5031 if (!rcr) return; /* Both positions invisible */
5032 if (pix->pixwindow) gdk_window_invalidate_rect(pix->pixwindow, rcr, FALSE);
5033 }
5034
5035 /* Input is two compiled-in XBMs */
wjpixmap_set_cursor(GtkWidget * widget,char * image,char * mask,int width,int height,int hot_x,int hot_y,int focused)5036 void wjpixmap_set_cursor(GtkWidget *widget, char *image, char *mask,
5037 int width, int height, int hot_x, int hot_y, int focused)
5038 {
5039 wjpixmap *pix = WJPIXMAP(widget);
5040
5041
5042 if (pix->cursor)
5043 {
5044 cairo_surface_fdestroy(pix->cursor);
5045 pix->cursor = NULL;
5046 }
5047 pix->focused_cursor = focused;
5048
5049 if (image)
5050 {
5051 GdkWindow *win = pix->pixwindow;
5052 cairo_surface_t *s;
5053 cairo_t *cr;
5054
5055 if (!win) win = gdk_screen_get_root_window(gtk_widget_get_screen(widget));
5056 pix->cursor = gdk_window_create_similar_surface(win,
5057 CAIRO_CONTENT_COLOR_ALPHA, width, height);
5058
5059 s = xbms_to_surface(image, mask, width, height);
5060 cr = cairo_create(pix->cursor);
5061 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
5062 cairo_set_source_surface(cr, s, 0, 0);
5063 cairo_paint(cr);
5064 cairo_destroy(cr);
5065 cairo_surface_fdestroy(s);
5066
5067 pix->cr.x = pix->xc - hot_x;
5068 pix->cr.y = pix->yc - hot_y;
5069 pix->cr.width = width;
5070 pix->cr.height = height;
5071 }
5072
5073 /* Optimizing redraw in a rare operation is useless */
5074 if (pix->pixmap) gtk_widget_queue_draw(widget);
5075 }
5076
5077 /* Translate allocation-relative coords to pixmap-relative */
wjpixmap_rxy(GtkWidget * widget,int x,int y,int * xr,int * yr)5078 int wjpixmap_rxy(GtkWidget *widget, int x, int y, int *xr, int *yr)
5079 {
5080 wjpixmap *pix = WJPIXMAP(widget);
5081
5082 if (!pix->pixmap) return (FALSE);
5083 x -= pix->pm.x;
5084 y -= pix->pm.y;
5085 *xr = x; *yr = y;
5086 return ((x >= 0) && (x < pix->pm.width) && (y >= 0) && (y < pix->pm.height));
5087 }
5088
5089 #else /* if GTK_MAJOR_VERSION <= 2 */
5090
5091 // Focusable pixmap widget
5092
5093 #define WJPIXMAP(obj) GTK_CHECK_CAST(obj, wjpixmap_get_type(), wjpixmap)
5094 #define IS_WJPIXMAP(obj) GTK_CHECK_TYPE(obj, wjpixmap_get_type())
5095
5096 typedef struct
5097 {
5098 GtkWidget widget; // Parent class
5099 GdkWindow *pixwindow;
5100 int width, height; // Requested pixmap size
5101 int xc, yc;
5102 int focused_cursor;
5103 GdkRectangle pm, cr;
5104 GdkPixmap *pixmap, *cursor;
5105 GdkBitmap *cmask;
5106 } wjpixmap;
5107
5108 static GtkType wjpixmap_type;
5109
5110 static GtkType wjpixmap_get_type();
5111
wjpixmap_position(wjpixmap * pix)5112 static void wjpixmap_position(wjpixmap *pix)
5113 {
5114 int x, y, w, h;
5115
5116 x = pix->widget.allocation.width;
5117 pix->pm.width = w = pix->width <= x ? pix->width : x;
5118 pix->pm.x = (x - w) / 2 + pix->widget.allocation.x;
5119 y = pix->widget.allocation.height;
5120 pix->pm.height = h = pix->height <= y ? pix->height : y;
5121 pix->pm.y = (y - h) / 2 + pix->widget.allocation.y;
5122 }
5123
wjpixmap_realize(GtkWidget * widget)5124 static void wjpixmap_realize(GtkWidget *widget)
5125 {
5126 wjpixmap *pix = WJPIXMAP(widget);
5127 GdkWindowAttr attrs;
5128
5129
5130 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
5131
5132 widget->window = gtk_widget_get_parent_window(widget);
5133 gdk_window_ref(widget->window);
5134
5135 wjpixmap_position(pix);
5136 attrs.x = pix->pm.x;
5137 attrs.y = pix->pm.y;
5138 attrs.width = pix->pm.width;
5139 attrs.height = pix->pm.height;
5140 attrs.window_type = GDK_WINDOW_CHILD;
5141 attrs.wclass = GDK_INPUT_OUTPUT;
5142 attrs.visual = gtk_widget_get_visual(widget);
5143 attrs.colormap = gtk_widget_get_colormap(widget);
5144 // Add the same events as GtkEventBox does
5145 attrs.event_mask = gtk_widget_get_events(widget) | GDK_BUTTON_MOTION_MASK |
5146 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
5147 GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
5148 pix->pixwindow = gdk_window_new(widget->window, &attrs,
5149 GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP);
5150 gdk_window_set_user_data(pix->pixwindow, widget);
5151 #if GTK_MAJOR_VERSION == 1
5152 fix_gdk_events(pix->pixwindow);
5153 #endif
5154 gdk_window_set_back_pixmap(pix->pixwindow, NULL, FALSE);
5155
5156 widget->style = gtk_style_attach(widget->style, widget->window);
5157 }
5158
wjpixmap_unrealize(GtkWidget * widget)5159 static void wjpixmap_unrealize(GtkWidget *widget)
5160 {
5161 wjpixmap *pix = WJPIXMAP(widget);
5162
5163 if (pix->pixwindow)
5164 {
5165 gdk_window_set_user_data(pix->pixwindow, NULL);
5166 gdk_window_destroy(pix->pixwindow);
5167 pix->pixwindow = NULL;
5168 }
5169
5170 if (widget_class->unrealize) widget_class->unrealize(widget);
5171 }
5172
wjpixmap_map(GtkWidget * widget)5173 static void wjpixmap_map(GtkWidget *widget)
5174 {
5175 wjpixmap *pix = WJPIXMAP(widget);
5176
5177 if (widget_class->map) widget_class->map(widget);
5178 if (pix->pixwindow) gdk_window_show(pix->pixwindow);
5179 }
5180
wjpixmap_unmap(GtkWidget * widget)5181 static void wjpixmap_unmap(GtkWidget *widget)
5182 {
5183 wjpixmap *pix = WJPIXMAP(widget);
5184
5185 if (pix->pixwindow) gdk_window_hide(pix->pixwindow);
5186 if (widget_class->unmap) widget_class->unmap(widget);
5187 }
5188
wjpixmap_size_request(GtkWidget * widget,GtkRequisition * requisition)5189 static void wjpixmap_size_request(GtkWidget *widget, GtkRequisition *requisition)
5190 {
5191 wjpixmap *pix = WJPIXMAP(widget);
5192 gint xp, yp;
5193
5194 #if GTK_MAJOR_VERSION == 1
5195 xp = widget->style->klass->xthickness * 2 + 4;
5196 yp = widget->style->klass->ythickness * 2 + 4;
5197 #else
5198 gtk_widget_style_get(GTK_WIDGET (widget),
5199 "focus-line-width", &xp, "focus-padding", &yp, NULL);
5200 yp = (xp + yp) * 2 + 2;
5201 xp = widget->style->xthickness * 2 + yp;
5202 yp = widget->style->ythickness * 2 + yp;
5203 #endif
5204 requisition->width = pix->width + xp;
5205 requisition->height = pix->height + yp;
5206 }
5207
wjpixmap_size_allocate(GtkWidget * widget,GtkAllocation * allocation)5208 static void wjpixmap_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
5209 {
5210 wjpixmap *pix = WJPIXMAP(widget);
5211
5212 widget->allocation = *allocation;
5213 wjpixmap_position(pix);
5214
5215 if (GTK_WIDGET_REALIZED(widget)) gdk_window_move_resize(pix->pixwindow,
5216 pix->pm.x, pix->pm.y, pix->pm.width, pix->pm.height);
5217 }
5218
wjpixmap_paint(GtkWidget * widget,int inner,GdkRectangle * area)5219 static void wjpixmap_paint(GtkWidget *widget, int inner, GdkRectangle *area)
5220 {
5221 wjpixmap *pix = WJPIXMAP(widget);
5222 GdkRectangle cdest;
5223
5224
5225 #if GTK_MAJOR_VERSION == 1
5226 /* Preparation */
5227 gdk_gc_set_clip_rectangle(widget->style->black_gc, area);
5228 #endif
5229
5230 if (!inner) // Drawing borders
5231 {
5232 #if GTK_MAJOR_VERSION == 1
5233 gdk_window_clear_area(widget->window,
5234 area->x, area->y, area->width, area->height);
5235 #endif
5236 /* Frame */
5237 gdk_draw_rectangle(widget->window, widget->style->black_gc,
5238 FALSE, pix->pm.x - 1, pix->pm.y - 1,
5239 pix->pm.width + 1, pix->pm.height + 1);
5240 /* Focus rectangle */
5241 if (GTK_WIDGET_HAS_FOCUS(widget))
5242 gtk_paint_focus(widget->style, widget->window,
5243 #if GTK_MAJOR_VERSION == 2
5244 GTK_WIDGET_STATE(widget),
5245 #endif
5246 area, widget, NULL,
5247 widget->allocation.x, widget->allocation.y,
5248 widget->allocation.width - 1, widget->allocation.height - 1);
5249 }
5250
5251 while (inner) // Drawing pixmap & cursor
5252 {
5253 /* Contents pixmap */
5254 gdk_draw_pixmap(pix->pixwindow, widget->style->black_gc,
5255 pix->pixmap, area->x, area->y,
5256 area->x, area->y, area->width, area->height);
5257
5258 /* Cursor pixmap */
5259 if (!pix->cursor) break;
5260 if (pix->focused_cursor && !GTK_WIDGET_HAS_FOCUS(widget)) break;
5261 if (!gdk_rectangle_intersect(&pix->cr, area, &cdest)) break;
5262 if (pix->cmask)
5263 {
5264 gdk_gc_set_clip_mask(widget->style->black_gc, pix->cmask);
5265 gdk_gc_set_clip_origin(widget->style->black_gc,
5266 pix->cr.x, pix->cr.y);
5267 }
5268 gdk_draw_pixmap(pix->pixwindow, widget->style->black_gc,
5269 pix->cursor, cdest.x - pix->cr.x, cdest.y - pix->cr.y,
5270 cdest.x, cdest.y, cdest.width, cdest.height);
5271 if (pix->cmask)
5272 {
5273 gdk_gc_set_clip_mask(widget->style->black_gc, NULL);
5274 gdk_gc_set_clip_origin(widget->style->black_gc, 0, 0);
5275 }
5276 break;
5277 }
5278
5279 #if GTK_MAJOR_VERSION == 1
5280 /* Cleanup */
5281 gdk_gc_set_clip_rectangle(widget->style->black_gc, NULL);
5282 #endif
5283 }
5284
wjpixmap_expose(GtkWidget * widget,GdkEventExpose * event)5285 static gboolean wjpixmap_expose(GtkWidget *widget, GdkEventExpose *event)
5286 {
5287 if (GTK_WIDGET_DRAWABLE(widget)) wjpixmap_paint(widget,
5288 event->window != widget->window, &event->area);
5289 return (FALSE);
5290 }
5291
5292 #if GTK_MAJOR_VERSION == 1
5293
wjpixmap_draw(GtkWidget * widget,GdkRectangle * area)5294 static void wjpixmap_draw(GtkWidget *widget, GdkRectangle *area)
5295 {
5296 wjpixmap *pix = WJPIXMAP(widget);
5297 GdkRectangle ir;
5298
5299 if (!GTK_WIDGET_DRAWABLE(widget)) return;
5300 /* If inner window is touched */
5301 if (gdk_rectangle_intersect(area, &pix->pm, &ir))
5302 {
5303 ir.x -= pix->pm.x; ir.y -= pix->pm.y;
5304 wjpixmap_paint(widget, TRUE, &ir);
5305 /* If outer window isn't */
5306 if (!((area->width - ir.width) | (area->height - ir.height)))
5307 return;
5308 }
5309 wjpixmap_paint(widget, FALSE, area);
5310 }
5311
wjpixmap_draw_focus(GtkWidget * widget)5312 static void wjpixmap_draw_focus(GtkWidget *widget)
5313 {
5314 gtk_widget_draw(widget, NULL);
5315 }
5316
wjpixmap_focus_event(GtkWidget * widget,GdkEventFocus * event)5317 static gint wjpixmap_focus_event(GtkWidget *widget, GdkEventFocus *event)
5318 {
5319 if (event->in) GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
5320 else GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
5321 gtk_widget_draw_focus(widget);
5322 return (FALSE);
5323 }
5324
5325 #endif
5326
wjpixmap_destroy(GtkObject * object)5327 static void wjpixmap_destroy(GtkObject *object)
5328 {
5329 wjpixmap *pix = WJPIXMAP(object);
5330
5331 if (pix->pixmap) gdk_pixmap_unref(pix->pixmap);
5332 if (pix->cursor) gdk_pixmap_unref(pix->cursor);
5333 if (pix->cmask) gdk_bitmap_unref(pix->cmask);
5334 /* !!! Unlike attached handler, default handler can get called twice */
5335 pix->pixmap = pix->cursor = pix->cmask = NULL;
5336 GTK_OBJECT_CLASS(widget_class)->destroy(object);
5337 }
5338
wjpixmap_class_init(GtkWidgetClass * class)5339 static void wjpixmap_class_init(GtkWidgetClass *class)
5340 {
5341 widget_class = gtk_type_class(GTK_TYPE_WIDGET);
5342 GTK_OBJECT_CLASS(class)->destroy = wjpixmap_destroy;
5343 class->realize = wjpixmap_realize;
5344 class->unrealize = wjpixmap_unrealize;
5345 class->map = wjpixmap_map;
5346 class->unmap = wjpixmap_unmap;
5347 #if GTK_MAJOR_VERSION == 1
5348 class->draw = wjpixmap_draw;
5349 class->draw_focus = wjpixmap_draw_focus;
5350 class->focus_in_event = class->focus_out_event = wjpixmap_focus_event;
5351 #endif
5352 class->expose_event = wjpixmap_expose;
5353 class->size_request = wjpixmap_size_request;
5354 class->size_allocate = wjpixmap_size_allocate;
5355 }
5356
wjpixmap_get_type()5357 static GtkType wjpixmap_get_type()
5358 {
5359 if (!wjpixmap_type)
5360 {
5361 static const GtkTypeInfo wjpixmap_info = {
5362 "wjPixmap", sizeof(wjpixmap), sizeof(GtkWidgetClass),
5363 (GtkClassInitFunc)wjpixmap_class_init,
5364 // (GtkObjectInitFunc)wjpixmap_init,
5365 NULL, /* No instance init */
5366 NULL, NULL, NULL };
5367 wjpixmap_type = gtk_type_unique(GTK_TYPE_WIDGET, &wjpixmap_info);
5368 }
5369
5370 return (wjpixmap_type);
5371 }
5372
wjpixmap_new(int width,int height)5373 GtkWidget *wjpixmap_new(int width, int height)
5374 {
5375 GtkWidget *widget = gtk_widget_new(wjpixmap_get_type(), NULL);
5376 wjpixmap *pix = WJPIXMAP(widget);
5377
5378 GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_FOCUS | GTK_NO_WINDOW);
5379 pix->width = width; pix->height = height;
5380 return (widget);
5381 }
5382
5383 /* Must be called first to init, and afterwards to access pixmap */
wjpixmap_pixmap(GtkWidget * widget)5384 GdkPixmap *wjpixmap_pixmap(GtkWidget *widget)
5385 {
5386 wjpixmap *pix = WJPIXMAP(widget);
5387
5388 if (!pix->pixmap)
5389 {
5390 GdkWindow *win = NULL;
5391 int depth = -1;
5392 if (GTK_WIDGET_REALIZED(widget)) win = widget->window;
5393 else depth = gtk_widget_get_visual(widget)->depth;
5394 pix->pixmap = gdk_pixmap_new(win, pix->width, pix->height, depth);
5395 }
5396 return (pix->pixmap);
5397 }
5398
wjpixmap_draw_rgb(GtkWidget * widget,int x,int y,int w,int h,unsigned char * rgb,int step)5399 void wjpixmap_draw_rgb(GtkWidget *widget, int x, int y, int w, int h,
5400 unsigned char *rgb, int step)
5401 {
5402 wjpixmap *pix = WJPIXMAP(widget);
5403
5404 if (!pix->pixmap) return;
5405 gdk_draw_rgb_image(pix->pixmap, widget->style->black_gc,
5406 x, y, w, h, GDK_RGB_DITHER_NONE, rgb, step);
5407 #if GTK_MAJOR_VERSION == 1
5408 gtk_widget_queue_draw_area(widget, x + pix->pm.x, y + pix->pm.y, w, h);
5409 #else /* if GTK_MAJOR_VERSION == 2 */
5410 if (pix->pixwindow)
5411 {
5412 GdkRectangle wr = { x, y, w, h };
5413 gdk_window_invalidate_rect(pix->pixwindow, &wr, FALSE);
5414 }
5415 #endif
5416 }
5417
wjpixmap_move_cursor(GtkWidget * widget,int x,int y)5418 void wjpixmap_move_cursor(GtkWidget *widget, int x, int y)
5419 {
5420 wjpixmap *pix = WJPIXMAP(widget);
5421 GdkRectangle pm, ocr, tcr1, tcr2, *rcr = NULL;
5422 int dx = x - pix->xc, dy = y - pix->yc;
5423
5424 if (!(dx | dy)) return;
5425 ocr = pix->cr;
5426 pix->cr.x += dx; pix->cr.y += dy;
5427 pix->xc = x; pix->yc = y;
5428
5429 if (!pix->pixmap || !pix->cursor) return;
5430 if (pix->focused_cursor && !GTK_WIDGET_HAS_FOCUS(widget)) return;
5431
5432 /* Anything visible? */
5433 if (!GTK_WIDGET_VISIBLE(widget)) return;
5434 pm = pix->pm; pm.x = pm.y = 0;
5435 if (gdk_rectangle_intersect(&ocr, &pm, &tcr1)) rcr = &tcr1;
5436 if (gdk_rectangle_intersect(&pix->cr, &pm, &tcr2))
5437 {
5438 if (rcr) gdk_rectangle_union(&tcr1, &tcr2, rcr = &ocr);
5439 else rcr = &tcr2;
5440 }
5441 if (!rcr) return; /* Both positions invisible */
5442 #if GTK_MAJOR_VERSION == 1
5443 gtk_widget_queue_draw_area(widget,
5444 rcr->x + pix->pm.x, rcr->y + pix->pm.y, rcr->width, rcr->height);
5445 #else /* if GTK_MAJOR_VERSION == 2 */
5446 if (pix->pixwindow) gdk_window_invalidate_rect(pix->pixwindow, rcr, FALSE);
5447 #endif
5448 }
5449
wjpixmap_set_cursor(GtkWidget * widget,char * image,char * mask,int width,int height,int hot_x,int hot_y,int focused)5450 void wjpixmap_set_cursor(GtkWidget *widget, char *image, char *mask,
5451 int width, int height, int hot_x, int hot_y, int focused)
5452 {
5453 wjpixmap *pix = WJPIXMAP(widget);
5454
5455
5456 if (pix->cursor) gdk_pixmap_unref(pix->cursor);
5457 if (pix->cmask) gdk_bitmap_unref(pix->cmask);
5458 pix->cursor = pix->cmask = NULL;
5459 pix->focused_cursor = focused;
5460
5461 if (image)
5462 {
5463 GdkWindow *win = NULL;
5464 int depth = -1;
5465 if (GTK_WIDGET_REALIZED(widget)) win = widget->window;
5466 else depth = gtk_widget_get_visual(widget)->depth;
5467
5468 pix->cursor = gdk_pixmap_create_from_data(win, image,
5469 width, height, depth,
5470 &widget->style->white, &widget->style->black);
5471 pix->cr.x = pix->xc - hot_x;
5472 pix->cr.y = pix->yc - hot_y;
5473 pix->cr.width = width;
5474 pix->cr.height = height;
5475 if (mask) pix->cmask = gdk_bitmap_create_from_data(win, mask,
5476 width, height);
5477 }
5478
5479 /* Optimizing redraw in a rare operation is useless */
5480 if (pix->pixmap) gtk_widget_queue_draw(widget);
5481 }
5482
5483 /* Translate allocation-relative coords to pixmap-relative */
wjpixmap_rxy(GtkWidget * widget,int x,int y,int * xr,int * yr)5484 int wjpixmap_rxy(GtkWidget *widget, int x, int y, int *xr, int *yr)
5485 {
5486 wjpixmap *pix = WJPIXMAP(widget);
5487
5488 if (!pix->pixmap) return (FALSE);
5489 x -= pix->pm.x - widget->allocation.x;
5490 y -= pix->pm.y - widget->allocation.y;
5491 *xr = x; *yr = y;
5492 return ((x >= 0) && (x < pix->pm.width) && (y >= 0) && (y < pix->pm.height));
5493 }
5494
5495 #endif /* GTK+1&2 */
5496
5497 // Type of pathname
5498
path_type(char * path)5499 int path_type(char *path)
5500 {
5501 #ifdef WIN32
5502 return ((path[0] == '/') || (path[0] == '\\') ? PT_DRIVE_ABS :
5503 path[1] != ':' ? PT_REL :
5504 (path[2] != '/') && (path[2] != '\\') ? PT_DRIVE_REL : PT_ABS);
5505 #else
5506 return (path[0] != '/' ? PT_REL : PT_ABS);
5507 #endif
5508 }
5509
5510 // Convert pathname to absolute
5511
resolve_path(char * buf,int buflen,char * path)5512 char *resolve_path(char *buf, int buflen, char *path)
5513 {
5514 char wbuf[PATHBUF], *tmp, *src, *dest, *tm2 = "";
5515 int ch, dot, dots, pt;
5516
5517 pt = path_type(path);
5518 wbuf[0] = '\0';
5519 /* Relative name to absolute */
5520 if (pt != PT_ABS)
5521 {
5522 getcwd(wbuf, PATHBUF - 1);
5523 tm2 = DIR_SEP_STR;
5524 }
5525 #ifdef WIN32
5526 /* Drive-absolute pathname needs current drive */
5527 if (pt == PT_DRIVE_ABS) wbuf[2] = '\0';
5528 /* Drive-relative pathname needs drive's current directory */
5529 else if (pt == PT_DRIVE_REL)
5530 {
5531 char tbuf[PATHBUF];
5532
5533 tbuf[0] = path[0]; tbuf[1] = ':'; tbuf[2] = '\0';
5534 if (!chdir(tbuf)) getcwd(tbuf, PATHBUF - 1);
5535 chdir(wbuf);
5536 memcpy(wbuf, tbuf, PATHBUF);
5537 path += 2;
5538 }
5539 #endif
5540 tmp = wjstrcat(NULL, 0, "", 0, wbuf, tm2, path, NULL);
5541
5542 /* Canonicalize path the way "realpath -s" does, i.e., symlinks
5543 * followed by ".." will get resolved wrong - WJ */
5544 src = dest = tmp;
5545 dots = dot = 0;
5546 while (TRUE)
5547 {
5548 ch = *src++;
5549 #ifdef WIN32
5550 if (ch == '/') ch = DIR_SEP;
5551 #endif
5552 if (ch == '.') dots += dot;
5553 else if (!ch || (ch == DIR_SEP))
5554 {
5555 if ((dots > 0) && (dots < 4)) /* // /./ /../ */
5556 {
5557 dest -= dots;
5558 if (dots == 3) /* /../ */
5559 {
5560 *dest = '\0';
5561 if ((tm2 = strrchr(tmp, DIR_SEP)))
5562 dest = tm2;
5563 }
5564 /* Do not lose trailing separator */
5565 if (!ch) *dest++ = DIR_SEP;
5566 }
5567 dots = dot = 1;
5568 }
5569 else dots = dot = 0;
5570 *dest++ = ch;
5571 if (!ch) break;
5572 }
5573
5574 /* Return the result */
5575 if (buf)
5576 {
5577 strncpy(buf, tmp, buflen);
5578 buf[buflen - 1] = 0;
5579 free(tmp);
5580 tmp = buf;
5581 }
5582 return (tmp);
5583 }
5584
5585 // A (better) substitute for fnmatch(), in case one is needed
5586
5587 /* One is necessary in case of Win32 or GTK+ 2.0/2.2 */
5588 #if defined(WIN32) || ((GTK_MAJOR_VERSION == 2) && (GTK2VERSION < 4))
5589
5590 #ifdef WIN32
5591
5592 /* Convert everything to lowercase */
nxchar(const char ** str)5593 static gunichar nxchar(const char **str)
5594 {
5595 gunichar c = g_utf8_get_char(*str);
5596 *str = g_utf8_next_char(*str);
5597 return (g_unichar_tolower(c));
5598 }
5599
5600 /* Slash isn't an escape char in Windows */
5601 #define UNQUOTE_CHAR(C,P)
5602
5603 #else
5604
nxchar(const char ** str)5605 static gunichar nxchar(const char **str)
5606 {
5607 gunichar c = g_utf8_get_char(*str);
5608 *str = g_utf8_next_char(*str);
5609 return (c);
5610 }
5611
5612 #define UNQUOTE_CHAR(C,P) if ((C) == '\\') (C) = nxchar(P)
5613
5614 #endif
5615
match_char_set(const char ** maskp,gunichar cs)5616 static int match_char_set(const char **maskp, gunichar cs)
5617 {
5618 const char *mask = *maskp;
5619 gunichar ch, cstart, cend;
5620 int inv;
5621
5622 ch = *mask;
5623 if ((inv = (ch == '^') | (ch == '!'))) mask++;
5624 ch = nxchar(&mask);
5625 while (TRUE)
5626 {
5627 UNQUOTE_CHAR(ch, &mask);
5628 if (!ch) return (0); // Failed
5629 cstart = cend = ch;
5630 if ((*mask == '-') && (mask[1] != ']'))
5631 {
5632 mask++;
5633 ch = nxchar(&mask);
5634 UNQUOTE_CHAR(ch, &mask);
5635 if (!ch) return (0); // Failed
5636 cend = ch;
5637 }
5638 if ((cs >= cstart) && (cs <= cend))
5639 {
5640 if (inv) return (-1); // Didn't match
5641 while ((ch = nxchar(&mask)) != ']')
5642 {
5643 UNQUOTE_CHAR(ch, &mask);
5644 if (!ch) return (0); // Failed
5645 }
5646 break;
5647 }
5648 ch = nxchar(&mask);
5649 if (ch != ']') continue;
5650 if (!inv) return (-1); // Didn't match
5651 break;
5652 }
5653 *maskp = mask;
5654 return (1); // Matched
5655 }
5656
5657 /* The limiting of recursion to one level in this algorithm is based on the
5658 * observation that in case of a "*X*Y" subsequence, moving a match for "X" to
5659 * the right can not improve the outcome - so only the part after the last
5660 * encountered star may ever need to be rematched at another position - WJ */
5661
wjfnmatch(const char * mask,const char * str,int utf)5662 int wjfnmatch(const char *mask, const char *str, int utf)
5663 {
5664 char *xmask, *xstr;
5665 const char *nstr, *omask, *wmask, *tstr = NULL, *tmask = NULL;
5666 gunichar ch, cs, cw;
5667 int res, ret = FALSE;
5668
5669
5670 /* Convert locale to utf8 */
5671 if (!utf)
5672 {
5673 mask = xmask = g_locale_to_utf8((gchar *)mask, -1, NULL, NULL, NULL);
5674 str = xstr = g_locale_to_utf8((gchar *)str, -1, NULL, NULL, NULL);
5675 // Fail the match if conversion failed
5676 if (!xmask || !xstr) return (FALSE);
5677 }
5678
5679 while (TRUE)
5680 {
5681 nstr = str;
5682 cs = nxchar(&nstr);
5683 if (!cs) break;
5684
5685 omask = mask;
5686 ch = nxchar(&mask);
5687 if ((cs == DIR_SEP) && (ch != cs)) goto nomatch;
5688 if (ch == '?')
5689 {
5690 str = nstr;
5691 continue;
5692 }
5693 if (ch == '[')
5694 {
5695 str = nstr;
5696 res = match_char_set(&mask, cs);
5697 if (res < 0) goto nomatch;
5698 if (!res) goto fail;
5699 continue;
5700 }
5701 if (ch == '*')
5702 {
5703 while (TRUE)
5704 {
5705 omask = mask;
5706 ch = nxchar(&mask);
5707 if (!ch)
5708 {
5709 ret = !strchr(str, DIR_SEP);
5710 goto fail;
5711 }
5712 if (ch == '*') continue;
5713 if (ch != '?') break;
5714 cs = nxchar(&str);
5715 if (!cs || (cs == DIR_SEP)) goto fail;
5716 }
5717 }
5718 else
5719 {
5720 str = nstr;
5721 UNQUOTE_CHAR(ch, &mask);
5722 if (ch == cs) continue;
5723 nomatch: if (!tmask) goto fail;
5724 omask = mask = tmask;
5725 str = tstr;
5726 ch = nxchar(&mask);
5727 }
5728 cw = ch;
5729 UNQUOTE_CHAR(ch, &mask);
5730 if (!ch) goto fail; // Escape char at end of mask
5731 wmask = mask;
5732 while (TRUE)
5733 {
5734 cs = nxchar(&str);
5735 if (!cs || ((cs == DIR_SEP) && (ch != cs))) goto fail;
5736 if (cw == '[')
5737 {
5738 res = match_char_set(&mask, cs);
5739 if (res > 0) break;
5740 if (!res) goto fail;
5741 mask = wmask;
5742 }
5743 else if (ch == cs) break;
5744 }
5745 tmask = omask;
5746 tstr = str;
5747 }
5748 while ((ch = nxchar(&mask)) == '*');
5749 ret = !ch;
5750
5751 fail: if (!utf)
5752 {
5753 g_free(xmask);
5754 g_free(xstr);
5755 }
5756 return (ret);
5757 }
5758
5759 #endif
5760
5761 // Replace '/' path separators
5762
5763 #ifdef WIN32
5764
reseparate(char * str)5765 void reseparate(char *str)
5766 {
5767 while ((str = strchr(str, '/'))) *str++ = DIR_SEP;
5768 }
5769
5770 #endif
5771
5772 // Process event queue
5773
handle_events()5774 void handle_events()
5775 {
5776 int i = 20; /* To prevent endless waiting */
5777 while ((i-- > 0) && gtk_events_pending()) gtk_main_iteration();
5778 }
5779
5780 // Make GtkEntry accept Ctrl+Enter as a character
5781
convert_ctrl_enter(GtkWidget * widget,GdkEventKey * event,gpointer user_data)5782 static gboolean convert_ctrl_enter(GtkWidget *widget, GdkEventKey *event,
5783 gpointer user_data)
5784 {
5785 if (((event->keyval == KEY(Return)) || (event->keyval == KEY(KP_Enter))) &&
5786 (event->state & GDK_CONTROL_MASK))
5787 {
5788 #if GTK_MAJOR_VERSION == 1
5789 GtkEditable *edit = GTK_EDITABLE(widget);
5790 gint pos = edit->current_pos;
5791
5792 gtk_editable_delete_selection(edit);
5793 gtk_editable_insert_text(edit, "\n", 1, &pos);
5794 edit->current_pos = pos;
5795 #else /* if GTK_MAJOR_VERSION >= 2 */
5796 gtk_signal_emit_by_name(GTK_OBJECT(widget), "insert_at_cursor", "\n");
5797 #endif
5798 return (TRUE);
5799 }
5800 return (FALSE);
5801 }
5802
accept_ctrl_enter(GtkWidget * entry)5803 void accept_ctrl_enter(GtkWidget *entry)
5804 {
5805 gtk_signal_connect(GTK_OBJECT(entry), "key_press_event",
5806 GTK_SIGNAL_FUNC(convert_ctrl_enter), NULL);
5807 }
5808
5809 // Grab/ungrab input
5810
5811 #define GRAB_KEY "mtPaint.grab"
5812
5813 /* !!! Widget is expected to have window visible & w/appropriate event masks */
do_grab(int mode,GtkWidget * widget,GdkCursor * cursor)5814 int do_grab(int mode, GtkWidget *widget, GdkCursor *cursor)
5815 {
5816 int owner_events = mode != GRAB_FULL;
5817
5818 #if GTK_MAJOR_VERSION == 1
5819 if (!widget->window) return (FALSE);
5820 if (gdk_keyboard_grab(widget->window, owner_events, GDK_CURRENT_TIME))
5821 return (FALSE);
5822 if (gdk_pointer_grab(widget->window, owner_events,
5823 GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK,
5824 NULL, cursor, GDK_CURRENT_TIME))
5825 {
5826 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
5827 return (FALSE);
5828 }
5829 if (mode != GRAB_PROGRAM) gtk_grab_add(widget);
5830 #elif GTK_MAJOR_VERSION == 2
5831 guint32 time;
5832
5833 if (!widget->window) return (FALSE);
5834 time = gtk_get_current_event_time();
5835 if (gdk_keyboard_grab(widget->window, owner_events, time) != GDK_GRAB_SUCCESS)
5836 return (FALSE);
5837
5838 if (gdk_pointer_grab(widget->window, owner_events,
5839 GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK,
5840 NULL, cursor, time) != GDK_GRAB_SUCCESS)
5841 {
5842 gdk_display_keyboard_ungrab(gtk_widget_get_display(widget), time);
5843 return (FALSE);
5844 }
5845 if (mode != GRAB_PROGRAM) gtk_grab_add(widget);
5846 #else /* #if GTK_MAJOR_VERSION == 3 */
5847 guint32 time;
5848 GdkDevice *kbd, *mouse, *dev;
5849 GdkWindow *win = gtk_widget_get_window(widget);
5850
5851 if (!win) return (FALSE); // Lose
5852 time = gtk_get_current_event_time();
5853 mouse = dev = gtk_get_current_event_device();
5854 if (!dev) return (FALSE); // If called out of the blue by mistake
5855 kbd = gdk_device_get_associated_device(dev);
5856 if (gdk_device_get_source(dev) == GDK_SOURCE_KEYBOARD) // Missed the guess
5857 mouse = kbd , kbd = dev;
5858
5859 if (gdk_device_grab(kbd, win, GDK_OWNERSHIP_APPLICATION, owner_events,
5860 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, NULL, time) != GDK_GRAB_SUCCESS)
5861 return (FALSE);
5862 if (gdk_device_grab(mouse, win, GDK_OWNERSHIP_APPLICATION, owner_events,
5863 GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
5864 GDK_POINTER_MOTION_MASK, cursor, time) != GDK_GRAB_SUCCESS)
5865 {
5866 gdk_device_ungrab(kbd, time);
5867 return (FALSE);
5868 }
5869 if (mode != GRAB_PROGRAM) gtk_device_grab_add(widget, mouse, TRUE);
5870 g_object_set_data(G_OBJECT(widget), GRAB_KEY, mouse);
5871 #endif
5872 return (TRUE);
5873 }
5874
undo_grab(GtkWidget * widget)5875 void undo_grab(GtkWidget *widget)
5876 {
5877 #if GTK_MAJOR_VERSION == 1
5878 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
5879 gdk_pointer_ungrab(GDK_CURRENT_TIME);
5880 gtk_grab_remove(widget);
5881 #elif GTK_MAJOR_VERSION == 2
5882 guint32 time = gtk_get_current_event_time();
5883 GdkDisplay *display = gtk_widget_get_display(widget);
5884 gdk_display_keyboard_ungrab(display, time);
5885 gdk_display_pointer_ungrab(display, time);
5886 gtk_grab_remove(widget);
5887 #else /* #if GTK_MAJOR_VERSION == 3 */
5888 /* !!! Only removes what do_grab() set, here */
5889 guint32 time = gtk_get_current_event_time();
5890 GdkDevice *mouse = g_object_get_data(G_OBJECT(widget), GRAB_KEY);
5891 if (!mouse) return;
5892 gdk_device_ungrab(gdk_device_get_associated_device(mouse), time); // kbd
5893 gdk_device_ungrab(mouse, time);
5894 gtk_device_grab_remove(widget, mouse);
5895 g_object_set_data(G_OBJECT(widget), GRAB_KEY, NULL);
5896 #endif
5897 }
5898
5899 #if GTK_MAJOR_VERSION == 1
5900
5901 // Workaround for crazy GTK+1 resize handling
5902
5903 /* !!! GTK+1 may "short-circuit" a resize just redoing an existing allocation -
5904 * unless resize is queued on a toplevel and NOT from within its check_resize
5905 * handler. As all size_request and size_allocate handlers ARE called from
5906 * within it, here is a way to postpone queuing till after it finishes - WJ */
5907
5908 #define RESIZE_KEY "mtPaint.resize"
5909
5910 static guint resize_key;
5911
repeat_resize(GtkContainer * cont,gpointer user_data)5912 static void repeat_resize(GtkContainer *cont, gpointer user_data)
5913 {
5914 GtkObject *obj = GTK_OBJECT(cont);
5915
5916 if (gtk_object_get_data_by_id(obj, resize_key) != (gpointer)2) return;
5917 gtk_object_set_data_by_id(obj, resize_key, (gpointer)1);
5918 gtk_widget_queue_resize(GTK_WIDGET(cont));
5919 }
5920
force_resize(GtkWidget * widget)5921 void force_resize(GtkWidget *widget)
5922 {
5923 GtkObject *obj = GTK_OBJECT(gtk_widget_get_toplevel(widget));
5924
5925 if (!resize_key) resize_key = g_quark_from_static_string(RESIZE_KEY);
5926 if (!gtk_object_get_data_by_id(obj, resize_key))
5927 gtk_signal_connect_after(obj, "check_resize",
5928 GTK_SIGNAL_FUNC(repeat_resize), NULL);
5929 gtk_object_set_data_by_id(obj, resize_key, (gpointer)2);
5930 }
5931
5932 // Workaround for broken GTK_SHADOW_NONE viewports in GTK+1
5933
5934 /* !!! gtk_viewport_draw() adjusts for shadow thickness even with shadow
5935 * disabled, resulting in lower right boundary left undrawn; easiest fix is
5936 * to set thickness to 0 for such widgets - WJ */
5937
vport_noshadow_fix(GtkWidget * widget)5938 void vport_noshadow_fix(GtkWidget *widget)
5939 {
5940 static GtkStyle *defstyle;
5941 GtkStyleClass *class;
5942
5943 if (!defstyle)
5944 {
5945 defstyle = gtk_style_new();
5946 class = g_malloc(sizeof(GtkStyleClass));
5947 memcpy(class, defstyle->klass, sizeof(GtkStyleClass));
5948 defstyle->klass = class;
5949 class->xthickness = class->ythickness = 0;
5950 }
5951 gtk_widget_set_style(widget, defstyle);
5952 }
5953
5954 #endif
5955
5956 // Helper for accessing scrollbars
5957
get_scroll_adjustments(GtkWidget * win,GtkAdjustment ** h,GtkAdjustment ** v)5958 void get_scroll_adjustments(GtkWidget *win, GtkAdjustment **h, GtkAdjustment **v)
5959 {
5960 GtkScrolledWindow *scroll = GTK_SCROLLED_WINDOW(win);
5961 *h = gtk_scrolled_window_get_hadjustment(scroll);
5962 *v = gtk_scrolled_window_get_vadjustment(scroll);
5963 }
5964
5965 // Helper for widget show/hide
5966
widget_showhide(GtkWidget * widget,int what)5967 void widget_showhide(GtkWidget *widget, int what)
5968 {
5969 (what ? gtk_widget_show : gtk_widget_hide)(widget);
5970 }
5971
5972 // Color name to value
5973
parse_color(char * what)5974 int parse_color(char *what)
5975 {
5976 #if GTK_MAJOR_VERSION == 3
5977 GdkRGBA col;
5978
5979 if (!gdk_rgba_parse(&col, what)) return (-1);
5980 return (RGB_2_INT(((int)(col.red * 65535) + 128) / 257,
5981 ((int)(col.green * 65535) + 128) / 257,
5982 ((int)(col.blue * 65535) + 128) / 257));
5983 #else /* if GTK_MAJOR_VERSION <= 2 */
5984 GdkColor col;
5985
5986 if (!gdk_color_parse(what, &col)) return (-1);
5987 return (RGB_2_INT(((int)col.red + 128) / 257,
5988 ((int)col.green + 128) / 257,
5989 ((int)col.blue + 128) / 257));
5990 #endif
5991 }
5992
5993 // DPI value
5994
window_dpi(GtkWidget * win)5995 double window_dpi(GtkWidget *win)
5996 {
5997 #if GTK_MAJOR_VERSION == 3
5998 GValue v;
5999 GdkScreen *sc = gtk_widget_get_screen(win);
6000 double d = gdk_screen_get_resolution(sc);
6001 if (d > 0) return (d); // Seems good
6002
6003 memset(&v, 0, sizeof(v));
6004 g_value_init(&v, G_TYPE_INT);
6005 if (gdk_screen_get_setting(sc, "gtk-xft-dpi", &v))
6006 return (g_value_get_int(&v) / (double)1024.0);
6007 #ifdef GDK_WINDOWING_X11
6008 {
6009 /* Get DPI from Xft */
6010 char *e, *v = XGetDefault(GDK_SCREEN_XDISPLAY(sc), "Xft", "dpi");
6011 if (v)
6012 {
6013 d = g_strtod(v, &e);
6014 if (e != v) return (d); // Seems good
6015 }
6016 }
6017 #endif
6018 return ((gdk_screen_get_height(sc) * (double)25.4) / gdk_screen_get_height_mm(sc));
6019 #else /* if GTK_MAJOR_VERSION <= 2 */
6020 #if GTK2VERSION >= 10
6021 double d = gdk_screen_get_resolution(gdk_drawable_get_screen(win->window));
6022 if (d > 0) return (d); // Seems good
6023 #endif
6024 #if GTK2VERSION >= 4
6025 {
6026 GValue v;
6027 memset(&v, 0, sizeof(v));
6028 g_value_init(&v, G_TYPE_INT);
6029 if (gdk_screen_get_setting(gdk_drawable_get_screen(win->window),
6030 "gtk-xft-dpi", &v))
6031 return (g_value_get_int(&v) / (double)1024.0);
6032 }
6033 #endif
6034 #if defined(U_MTK) || defined(GDK_WINDOWING_X11) /* GTK+2/X */
6035 {
6036 /* Get DPI from Xft */
6037 char *e, *v = XGetDefault(GDK_WINDOW_XDISPLAY(win->window),
6038 "Xft", "dpi");
6039 if (v)
6040 {
6041 double d = g_strtod(v, &e);
6042 if (e != v) return (d); // Seems good
6043 }
6044 }
6045 #endif
6046 #if GTK2VERSION >= 2
6047 {
6048 GdkScreen *sc = gdk_drawable_get_screen(win->window);
6049 return ((gdk_screen_get_height(sc) * (double)25.4) /
6050 gdk_screen_get_height_mm(sc));
6051 }
6052 #else
6053 return ((gdk_screen_height() * (double)25.4) /
6054 gdk_screen_height_mm());
6055 #endif
6056 #endif /* GTK+1&2 */
6057 }
6058
6059 // Memory size (Mb)
6060
6061 #ifndef WIN32
6062 #ifndef _SC_PHYS_PAGES
6063 #include <sys/types.h>
6064 #include <sys/param.h>
6065 #include <sys/sysctl.h>
6066 #endif
6067 #endif
6068
sys_mem_size()6069 unsigned sys_mem_size()
6070 {
6071 #ifdef WIN32
6072 MEMORYSTATUS mem;
6073 mem.dwLength = sizeof(mem);
6074 GlobalMemoryStatus(&mem);
6075 return (mem.dwTotalPhys / (1024 * 1024));
6076 #elif defined _SC_PHYS_PAGES
6077 size_t n;
6078 #ifdef _SC_PAGESIZE
6079 n = sysconf(_SC_PAGESIZE);
6080 #else
6081 n = sysconf(_SC_PAGE_SIZE);
6082 #endif
6083 return (((n / 1024) * sysconf(_SC_PHYS_PAGES)) / 1024);
6084 #elif defined CTL_HW
6085 #undef FAIL
6086 int mib[2] = { CTL_HW };
6087 size_t n;
6088 #ifdef HW_MEMSIZE
6089 uint64_t v;
6090 mib[1] = HW_MEMSIZE;
6091 #elif defined HW_PHYSMEM64
6092 uint64_t v;
6093 mib[1] = HW_PHYSMEM64;
6094 #elif defined HW_REALMEM
6095 unsigned long v;
6096 mib[1] = HW_REALMEM;
6097 #elif defined HW_PHYSMEM
6098 unsigned long v;
6099 mib[1] = HW_PHYSMEM;
6100 #else
6101 #define FAIL
6102 #endif
6103 #ifndef FAIL
6104 n = sizeof(v);
6105 if (!sysctl(mib, 2, &v, &n, NULL, 0) && (n == sizeof(v)))
6106 return ((unsigned)(v / (1024 * 1024)));
6107 #endif
6108 #endif
6109 return (0); // Fail
6110 }
6111
6112 // Threading helpers
6113
6114 #if 0 /* Not needed for now - GTK+/Win32 still isn't thread-safe anyway */
6115 //#ifdef U_THREADS
6116
6117 /* The following is a replacement for gdk_threads_add_*() functions - which are
6118 * useful, but hadn't been implemented before GTK+ 2.12 - WJ */
6119
6120 #if GTK_MAJOR_VERSION == 1
6121
6122 /* With GLib 1.2, a g_source_is_destroyed()-like check cannot be done from
6123 * outside GLib, so thread-safety is limited (see GTK+ bug #321866) */
6124
6125 typedef struct {
6126 GSourceFunc callback;
6127 gpointer user_data;
6128 } dispatch_info;
6129
6130 static gboolean do_dispatch(dispatch_info *info)
6131 {
6132 gboolean res;
6133
6134 gdk_threads_enter();
6135 res = info->callback(info->user_data);
6136 gdk_threads_leave();
6137 return (res);
6138 }
6139
6140 guint threads_idle_add_priority(gint priority, GtkFunction function, gpointer data)
6141 {
6142 dispatch_info *disp = g_malloc(sizeof(dispatch_info));
6143
6144 disp->callback = function;
6145 disp->user_data = data;
6146 return (g_idle_add_full(priority, (GSourceFunc)do_dispatch, disp,
6147 (GDestroyNotify)g_free));
6148 }
6149
6150 guint threads_timeout_add(guint32 interval, GSourceFunc function, gpointer data)
6151 {
6152 dispatch_info *disp = g_malloc(sizeof(dispatch_info));
6153
6154 disp->callback = function;
6155 disp->user_data = data;
6156 return (g_timeout_add_full(G_PRIORITY_DEFAULT, interval,
6157 (GSourceFunc)do_dispatch, disp, (GDestroyNotify)g_free));
6158 }
6159
6160 #else /* if GTK_MAJOR_VERSION == 2 */
6161
6162 static GSourceFuncs threads_timeout_funcs, threads_idle_funcs;
6163
6164 static gboolean threads_timeout_dispatch(GSource *source, GSourceFunc callback,
6165 gpointer user_data)
6166 {
6167 gboolean res = FALSE;
6168
6169 gdk_threads_enter();
6170 /* The test below is what g_source_is_destroyed() in GLib 2.12+ does */
6171 if (source->flags & G_HOOK_FLAG_ACTIVE)
6172 res = g_timeout_funcs.dispatch(source, callback, user_data);
6173 gdk_threads_leave();
6174 return (res);
6175 }
6176
6177 static gboolean threads_idle_dispatch(GSource *source, GSourceFunc callback,
6178 gpointer user_data)
6179 {
6180 gboolean res = FALSE;
6181
6182 gdk_threads_enter();
6183 /* The test below is what g_source_is_destroyed() in GLib 2.12+ does */
6184 if (source->flags & G_HOOK_FLAG_ACTIVE)
6185 res = g_idle_funcs.dispatch(source, callback, user_data);
6186 gdk_threads_leave();
6187 return (res);
6188 }
6189
6190 guint threads_timeout_add(guint32 interval, GSourceFunc function, gpointer data)
6191 {
6192 GSource *source = g_timeout_source_new(interval);
6193 guint id;
6194
6195 if (!threads_timeout_funcs.dispatch)
6196 {
6197 threads_timeout_funcs = g_timeout_funcs;
6198 threads_timeout_funcs.dispatch = threads_timeout_dispatch;
6199 }
6200 source->source_funcs = &threads_timeout_funcs;
6201
6202 g_source_set_callback(source, function, data, NULL);
6203 id = g_source_attach(source, NULL);
6204 g_source_unref(source);
6205
6206 return (id);
6207 }
6208
6209 guint threads_idle_add_priority(gint priority, GtkFunction function, gpointer data)
6210 {
6211 GSource *source = g_idle_source_new();
6212 guint id;
6213
6214 if (!threads_idle_funcs.dispatch)
6215 {
6216 threads_idle_funcs = g_idle_funcs;
6217 threads_idle_funcs.dispatch = threads_idle_dispatch;
6218 }
6219 source->source_funcs = &threads_idle_funcs;
6220
6221 if (priority != G_PRIORITY_DEFAULT_IDLE)
6222 g_source_set_priority(source, priority);
6223 g_source_set_callback(source, function, data, NULL);
6224 id = g_source_attach(source, NULL);
6225 g_source_unref(source);
6226
6227 return (id);
6228 }
6229
6230 #endif
6231
6232 #endif
6233