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