1 /*	vcode.c
2 	Copyright (C) 2013-2021 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 
22 #include "mygtk.h"
23 #include "memory.h"
24 #include "vcode.h"
25 #include "inifile.h"
26 #include "png.h"
27 #include "mainwindow.h"
28 #include "otherwindow.h"
29 #include "canvas.h"
30 #include "cpick.h"
31 #include "icons.h"
32 #include "fpick.h"
33 #include "prefs.h"
34 
35 /// More of meaningless differences to hide
36 
37 #if GTK_MAJOR_VERSION == 3
38 #define gdk_rgb_init() /* Not needed anymore */
39 #define gtk_object_get_data(A,B) g_object_get_data(A,B)
40 #define gtk_object_set_data(A,B,C) g_object_set_data(A,B,C)
41 #define gtk_object_get_data_by_id(A,B) g_object_get_qdata(A,B)
42 #define gtk_object_set_data_by_id(A,B,C) g_object_set_qdata(A,B,C)
43 #define GtkObject GObject
44 #define gtk_object_weakref(A,B,C) g_object_weak_ref(A,B,C)
45 #define GtkDestroyNotify GWeakNotify
46 #define GTK_WIDGET_REALIZED(A) gtk_widget_get_realized(A)
47 #define GTK_WIDGET_MAPPED(A) gtk_widget_get_mapped(A)
48 #define	GTK_WIDGET_SENSITIVE(A) gtk_widget_get_sensitive(A)
49 #define	GTK_WIDGET_IS_SENSITIVE(A) gtk_widget_is_sensitive(A)
50 #define GTK_WIDGET_VISIBLE(A) gtk_widget_get_visible(A)
51 #define gtk_notebook_set_page gtk_notebook_set_current_page
52 #define gtk_accel_group_unref g_object_unref
53 #define gtk_hpaned_new() gtk_paned_new(GTK_ORIENTATION_HORIZONTAL)
54 #define gtk_vpaned_new() gtk_paned_new(GTK_ORIENTATION_VERTICAL)
55 #define gtk_hseparator_new() gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)
56 #define gtk_vseparator_new() gtk_separator_new(GTK_ORIENTATION_VERTICAL)
57 #define hbox_new(A) gtk_box_new(GTK_ORIENTATION_HORIZONTAL, (A))
58 #define vbox_new(A) gtk_box_new(GTK_ORIENTATION_VERTICAL, (A))
59 #define gtk_menu_item_right_justify(A) gtk_menu_item_set_right_justified((A), TRUE)
60 #define gtk_radio_menu_item_group gtk_radio_menu_item_get_group
61 #define gtk_widget_ref(A) g_object_ref(A)
62 #define gtk_widget_unref(A) g_object_unref(A)
63 
64 static GQuark tool_key;
65 #define TOOL_KEY "mtPaint.tool"
66 
67 /* Running in a wheel is for hamsters, and pointless churn is pointless */
68 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
69 
70 #else
71 #define gtk_selection_data_get_data(A) ((A)->data)
72 #define gtk_selection_data_get_length(A) ((A)->length)
73 #define gtk_selection_data_get_format(A) ((A)->format)
74 #define gtk_selection_data_get_target(A) ((A)->target)
75 #define gtk_selection_data_get_data_type(A) ((A)->type)
76 #define gtk_bin_get_child(A) ((A)->child)
77 #define gtk_text_view_get_buffer(A) ((A)->buffer)
78 #define gtk_dialog_get_action_area(A) ((A)->action_area)
79 #define gtk_dialog_get_content_area(A) ((A)->vbox)
80 #define gtk_font_selection_get_preview_entry(A) ((A)->preview_entry)
81 #define gtk_check_menu_item_get_active(A) ((A)->active)
82 #define gtk_adjustment_get_value(A) ((A)->value)
83 #define gtk_adjustment_get_upper(A) ((A)->upper)
84 #define gtk_adjustment_get_page_size(A) ((A)->page_size)
85 #define gtk_window_get_focus(A) ((A)->focus_widget)
86 #define gtk_paned_get_child1(A) ((A)->child1)
87 #define gtk_paned_get_child2(A) ((A)->child2)
88 #define gtk_paned_get_position(A) ((A)->child1_size)
89 #define gtk_menu_item_get_submenu(A) ((A)->submenu)
90 #define hbox_new(A) gtk_hbox_new(FALSE, (A))
91 #define vbox_new(A) gtk_vbox_new(FALSE, (A))
92 
93 #endif
94 
95 /* Make code not compile if it cannot work */
96 typedef char Opcodes_Too_Long[2 * (op_LAST <= WB_OPMASK) - 1];
97 
98 /// V-CODE ENGINE
99 
100 /* Max V-code subroutine nesting */
101 #define CALL_DEPTH 16
102 /* Max container widget nesting */
103 #define CONT_DEPTH 128
104 /* Max columns in a list */
105 #define MAX_COLS 16
106 /* Max keys in keymap */
107 #define MAX_KEYS 512
108 
109 #define VVS(N) (((N) + sizeof(void *) - 1) / sizeof(void *))
110 
111 #define GET_OPF(S) ((int)*(void **)(S)[1])
112 #define GET_OP(S) (GET_OPF(S) & WB_OPMASK)
113 
114 #define GET_DESCV(S,N) (((void **)(S)[1])[(N)])
115 #define GET_HANDLER(S) GET_DESCV(S, 1)
116 
117 #if VSLOT_SIZE != 3
118 #error "Mismatched slot size"
119 #endif
120 // !!! V should never be NULL - IS_UNREAL() relies on that
121 #define EVSLOT(P,S,V) { (V), &(P), NULL, (S) }
122 #define EV_SIZE (VSLOT_SIZE + 1)
123 #define EV_PARENT(S) S[VSLOT_SIZE]
124 
125 #define VCODE_KEY "mtPaint.Vcode"
126 
127 /* Internal datastore */
128 
129 #define GET_VDATA(V) ((V)[1])
130 
131 typedef struct {
132 	void *code;	// Noop tag, must be first field
133 	void ***dv;	// Pointer to dialog response
134 	void **destroy;	// Pre-destruction event slot
135 	void **wantkey;	// Slot of prioritized keyboard handler
136 	void **keymap;	// KEYMAP slot
137 	void **smmenu;	// SMARTMENU slot
138 	void **fupslot;	// Slot which needs defocusing to update (only 1 for now)
139 	void **actmap;	// Actmap array
140 	void *now_evt;	// Keyboard event being handled (check against recursion)
141 	void *tparent;	// Transient parent window
142 	char *ininame;	// Prefix for inifile vars
143 	char **script;	// Commands if simulated
144 	int xywh[5];	// Stored window position/size/state
145 	int actn;	// Actmap slots count
146 	unsigned actmask;	// Last actmap mask
147 	unsigned vismask;	// Visibility mask
148 	char modal;	// Set modal flag when displaying
149 	char raise;	// Raise after displaying
150 	char unfocus;	// Focus to NULL after displaying
151 	char done;	// Set when destroyed
152 	char run;	// Set when script is running
153 } v_dd;
154 
155 /* Actmap array */
156 
157 #define ACT_SIZE 2 /* Pointers per actmap slot */
158 #define ADD_ACT(W,S,V) ((W)[0] = (S) , (W)[1] = (V))
159 
act_state(v_dd * vdata,unsigned int mask)160 static void act_state(v_dd *vdata, unsigned int mask)
161 {
162 	void **map = vdata->actmap;
163 	unsigned int n, m, vm = vdata->vismask;
164 	int i;
165 
166 	vdata->actmask = mask;
167 	i = vdata->actn;
168 	while (i-- > 0)
169 	{
170 		n = (unsigned)map[1];
171 		if ((m = n & vm)) cmd_showhide(map[0], !!(m & mask));
172 		if ((m = n & ~vm)) cmd_sensitive(map[0], !!(m & mask));
173 		map += ACT_SIZE;
174 	}
175 }
176 
177 /* Simulated widget - one struct for all kinds */
178 
179 typedef struct {
180 	char insens;	// Insensitivity flag
181 	short op;	// Internal opcode
182 	short cnt;	// Options count
183 	int value;	// Integer value
184 	int range[2];	// Value range
185 	char *id;	// Identifying string
186 	void *strs;	// Option labels / string value / anything else
187 } swdata;
188 
189 #define IS_UNREAL(S) ((S)[0] == (S)[2])
190 #define GET_UOP(S) (((swdata *)(S)[0])->op)
191 
set_uentry(swdata * sd,char * s)192 static char *set_uentry(swdata *sd, char *s)
193 {
194 	return (sd->strs = !s ? NULL :
195 		sd->value < 0 ? g_strdup(s) : g_strndup(s, sd->value));
196 }
197 
198 /* Command table */
199 
200 typedef struct {
201 	short op, size, uop;
202 } cmdef;
203 
204 static cmdef *cmds[op_LAST];
205 
206 /* From widget to its wdata */
get_wdata(GtkWidget * widget,char * id)207 void **get_wdata(GtkWidget *widget, char *id)
208 {
209 	return (gtk_object_get_data(GTK_OBJECT(widget), id ? id : VCODE_KEY));
210 }
211 
212 /* From slot to its wdata */
wdata_slot(void ** slot)213 void **wdata_slot(void **slot)
214 {
215 	while (TRUE)
216 	{
217 		int op = GET_OP(slot);
218 		// WDONE anchors wdata
219 		if (op == op_WDONE) return (slot);
220 		// EVTs link to it
221 		if ((op >= op_EVT_0) && (op <= op_EVT_LAST)) return (*slot);
222 		// EVs link to parent slot
223 		if ((op >= op_EV_0) && (op <= op_EV_LAST))
224 			slot = EV_PARENT(slot);
225 		// Other slots just repose in sequence
226 		else slot = PREV_SLOT(slot);
227 		/* And if not valid wdata, die by SIGSEGV in the end */
228 	}
229 }
230 
231 /* From event to its originator */
origin_slot(void ** slot)232 void **origin_slot(void **slot)
233 {
234 	while (TRUE)
235 	{
236 		int op = GET_OP(slot);
237 		if (op < op_EVT_0) return (slot);
238 		// EVs link to parent slot
239 		else if ((op >= op_EV_0) && (op <= op_EV_LAST))
240 			slot = EV_PARENT(slot);
241 		else slot = PREV_SLOT(slot);
242 		/* And if not valid wdata, die by SIGSEGV in the end */
243 	}
244 }
245 
246 /* From slot to its storage location */
slot_data(void ** slot,void * ddata)247 void *slot_data(void **slot, void *ddata)
248 {
249 	void **desc = slot[1], **v = desc[1];
250 	int opf = (int)desc[0];
251 
252 	if (opf & WB_FFLAG) v = (void *)(ddata + (int)v);
253 	if (opf & WB_NFLAG) v = *v; // dereference
254 	return (v);
255 }
256 
257 /* Find specific V-code _after_ this slot */
op_slot(void ** slot,int op)258 static void **op_slot(void **slot, int op)
259 {
260 	while ((slot = NEXT_SLOT(slot))[1])
261 	{
262 		int n = GET_OP(slot);
263 		// Found
264 		if (n == op) return (slot);
265 		// Till another origin slot
266 		if (n < op_EVT_0) break;
267 	}
268 	// Not found
269 	return (NULL);
270 }
271 
272 /* Find unreal slot before this one */
prev_uslot(void ** slot)273 static void **prev_uslot(void **slot)
274 {
275 	slot = origin_slot(PREV_SLOT(slot));
276 	if (!IS_UNREAL(slot)) slot = op_slot(slot, op_uALTNAME);
277 	return (slot);
278 }
279 
dialog_event(void * ddata,void ** wdata,int what,void ** where)280 void dialog_event(void *ddata, void **wdata, int what, void **where)
281 {
282 	v_dd *vdata = GET_VDATA(wdata);
283 
284 	if (((int)vdata->code & WB_OPMASK) != op_WDONE) return; // Paranoia
285 	if (vdata->dv) *vdata->dv = where;
286 }
287 
288 /* !!! Warning: handlers should not access datastore after window destruction!
289  * GTK+ refs objects for signal duration, but no guarantee every other toolkit
290  * will behave alike - WJ */
291 
get_evt_1(GtkObject * widget,gpointer user_data)292 static void get_evt_1(GtkObject *widget, gpointer user_data)
293 {
294 	void **slot = user_data;
295 	void **base = slot[0], **desc = slot[1];
296 
297 	((evt_fn)desc[1])(GET_DDATA(base), base, (int)desc[0] & WB_OPMASK, slot);
298 }
299 
get_evt_1_t(GtkObject * widget,gpointer user_data)300 static void get_evt_1_t(GtkObject *widget, gpointer user_data)
301 {
302 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
303 		get_evt_1(widget, user_data);
304 }
305 
do_evt_1_d(void ** slot)306 void do_evt_1_d(void **slot)
307 {
308 	void **base = slot[0], **desc = slot[1];
309 
310 	if (!desc[1]) run_destroy(base);
311 	else ((evt_fn)desc[1])(GET_DDATA(base), base, (int)desc[0] & WB_OPMASK, slot);
312 }
313 
get_evt_del(GtkWidget * widget,GdkEvent * event,gpointer user_data)314 static gboolean get_evt_del(GtkWidget *widget, GdkEvent *event, gpointer user_data)
315 {
316 	do_evt_1_d(user_data);
317 	return (TRUE); // it is for handler to decide, destroy it or not
318 }
319 
get_evt_conf(GtkWidget * widget,GdkEventConfigure * event,gpointer user_data)320 static gboolean get_evt_conf(GtkWidget *widget, GdkEventConfigure *event,
321 	gpointer user_data)
322 {
323 	get_evt_1(NULL, user_data);
324 	return (TRUE); // no use of its propagating
325 }
326 
get_evt_key(GtkWidget * widget,GdkEventKey * event,gpointer user_data)327 static gboolean get_evt_key(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
328 {
329 	void **slot = user_data;
330 	void **base = slot[0], **desc = slot[1];
331 	key_ext key = {
332 		event->keyval, low_key(event), real_key(event), event->state };
333 	int res = ((evtxr_fn)desc[1])(GET_DDATA(base), base,
334 		(int)desc[0] & WB_OPMASK, slot, &key);
335 #if GTK_MAJOR_VERSION == 1
336 	/* Return value alone doesn't stop GTK1 from running other handlers */
337 	if (res) gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
338 #endif
339 	return (!!res);
340 }
341 
342 static int check_smart_menu_keys(void *sdata, GdkEventKey *event);
343 
window_evt_key(GtkWidget * widget,GdkEventKey * event,gpointer user_data)344 static gboolean window_evt_key(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
345 {
346 	void **slot = user_data;
347 	v_dd *vdata = GET_VDATA((void **)slot[0]);
348 	gint res, res2, stop;
349 
350 	/* Do nothing if called recursively */
351 	if ((void *)event == vdata->now_evt) return (FALSE);
352 
353 	res = res2 = stop = 0;
354 	/* First, ask smart menu */
355 	if (vdata->smmenu) res = check_smart_menu_keys(vdata->smmenu, event);
356 
357 	/* Now, ask prioritized widget */
358 	while (!res && vdata->wantkey)
359 	{
360 		void *was_evt, **wslot = origin_slot(vdata->wantkey);
361 
362 		if (!GTK_WIDGET_MAPPED(wslot[0])) break; // not displayed
363 // !!! Maybe this check is enough by itself?
364 		if (!cmd_checkv(wslot, SLOT_FOCUSED)) break;
365 
366 		slot = NEXT_SLOT(vdata->wantkey);
367 		if (GET_HANDLER(slot) &&
368 			(res = stop = get_evt_key(widget, event, slot))) break;
369 
370 #if GTK_MAJOR_VERSION >= 2
371 		/* We let widgets in the focused part process the keys first */
372 		res = gtk_window_propagate_key_event(GTK_WINDOW(widget), event);
373 		if (res) break;
374 #endif
375 		/* Let default handlers have priority */
376 		// !!! Be ready to handle nested events
377 		was_evt = vdata->now_evt;
378 		vdata->now_evt = event;
379 
380 		gtk_signal_emit_by_name(GTK_OBJECT(widget), "key_press_event",
381 			event, &res);
382 		res2 = TRUE; // Default events checked already
383 
384 		vdata->now_evt = was_evt;
385 
386 		break;
387 	}
388 
389 	/* And only now, run own handler */
390 	slot = user_data;
391 	if (!res && GET_HANDLER(slot))
392 		res = stop = get_evt_key(widget, event, slot);
393 
394 	res |= res2;
395 #if GTK_MAJOR_VERSION == 1
396 	/* Return value alone doesn't stop GTK1 from running other handlers */
397 	if (res && !stop)
398 		gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
399 #endif
400 	return (res);
401 }
402 
403 //	Widget datastores
404 
405 typedef struct {
406 	GtkWidget *pane, *vbox;
407 } dock_data;
408 
409 #define HVSPLIT_MAX 4
410 
411 typedef struct {
412 	GtkWidget *box, *panes[2], *inbox[HVSPLIT_MAX];
413 	int cnt;
414 } hvsplit_data;
415 
416 //	Text renderer
417 
418 #if GTK_MAJOR_VERSION >= 2
419 
420 int texteng_aa = TRUE;
421 #if (GTK_MAJOR_VERSION == 3) || (GTK2VERSION >= 6)
422 #define FULLPANGO 1
423 int texteng_rot = TRUE;
424 int texteng_spc = TRUE;
425 #else
426 #define FULLPANGO 0
427 #define PangoMatrix int /* For rotate_box() */
428 #define PANGO_MATRIX_INIT 0
429 int texteng_rot = FALSE;
430 int texteng_spc = FALSE;
431 #endif
432 int texteng_lf = TRUE;
433 int texteng_dpi = TRUE;
434 
435 #else /* GTK+1 */
436 
437 #ifdef U_MTK
438 int texteng_aa = TRUE;
439 #else
440 int texteng_aa = FALSE;
441 #endif
442 int texteng_rot = FALSE;
443 int texteng_spc = FALSE;
444 int texteng_lf = FALSE;
445 int texteng_dpi = FALSE;
446 
447 #endif
448 
449 int texteng_con = FALSE;
450 
451 typedef struct {
452 	double sysdpi;
453 	int dpi;
454 #if GTK_MAJOR_VERSION == 3
455 	int lastsize, lastdpi, lastset;
456 #endif
457 } fontsel_data;
458 
459 #if GTK_MAJOR_VERSION >= 2
460 
461 /* Pango coords to pixels, inclusive */
rotate_box(PangoRectangle * lr,PangoMatrix * m)462 static void rotate_box(PangoRectangle *lr, PangoMatrix *m)
463 {
464 	double xx[4] = { lr->x, lr->x + lr->width, lr->x, lr->x + lr->width };
465 	double yy[4] = { lr->y, lr->y, lr->y + lr->height, lr->y + lr->height };
466 	double x_min, x_max, y_min, y_max;
467 	int i;
468 
469 
470 #if FULLPANGO
471 	if (m) for (i = 0; i < 4; i++)
472 	{
473 		double xt, yt;
474 		xt = xx[i] * m->xx + yy[i] * m->xy + m->x0;
475 		yt = xx[i] * m->yx + yy[i] * m->yy + m->y0;
476 		xx[i] = xt;
477 		yy[i] = yt;
478 	}
479 #endif
480 	x_min = x_max = xx[0];
481 	y_min = y_max = yy[0];
482 	for (i = 1; i < 4; i++)
483 	{
484 		if (x_min > xx[i]) x_min = xx[i];
485 		if (x_max < xx[i]) x_max = xx[i];
486 		if (y_min > yy[i]) y_min = yy[i];
487 		if (y_max < yy[i]) y_max = yy[i];
488 	}
489 
490 	lr->x = floor(x_min / PANGO_SCALE);
491 	lr->width = ceil(x_max / PANGO_SCALE) - lr->x;
492 	lr->y = floor(y_min / PANGO_SCALE);
493 	lr->height = ceil(y_max / PANGO_SCALE) - lr->y;
494 }
495 
496 #if GTK_MAJOR_VERSION == 3
497 
498 #define FONT_SIGNAL "style_updated"
499 
fontsel_style(GtkWidget * widget,gpointer user_data)500 static void fontsel_style(GtkWidget *widget, gpointer user_data)
501 {
502 	GtkStyleContext *ctx;
503 	PangoFontDescription *d;
504 	void **r = user_data;
505 	fontsel_data *fd = r[2];
506 	int sz, fontsize;
507 
508 	if (!fd->dpi || !fd->sysdpi)
509 	{
510 		fd->lastdpi = 0; // To force an update when DPI is set again
511 		return; // Do nothing else
512 	}
513 	ctx = gtk_widget_get_style_context(widget);
514 	gtk_style_context_get(ctx, gtk_style_context_get_state(ctx), "font", &d, NULL);
515 	sz = pango_font_description_get_size(d);
516 	fontsize = gtk_font_selection_get_size(GTK_FONT_SELECTION(r[0]));
517 
518 	if ((sz != fd->lastset) || (fd->dpi != fd->lastdpi) || (fontsize != fd->lastsize))
519 	{
520 		fd->lastdpi = fd->dpi;
521 		fd->lastsize = fontsize;
522 		sz = (fontsize * fd->dpi) / fd->sysdpi;
523 		pango_font_description_set_size(d, fd->lastset = sz);
524 		gtk_widget_override_font(widget, d); // To make widget show it
525 	}
526 	pango_font_description_free(d);
527 }
528 
529 #else
530 
531 #define FONT_SIGNAL "style_set"
532 
533 /* In principle, similar approach can be used with GTK+1 too - but it would be
534  * much less clean and less precise, and I am unsure about possibly wasting
535  * a lot of X font resources; therefore, no DPI for GTK+1 - WJ */
fontsel_style(GtkWidget * widget,GtkStyle * previous_style,gpointer user_data)536 static void fontsel_style(GtkWidget *widget, GtkStyle *previous_style,
537 	gpointer user_data)
538 {
539 	PangoContext *c;
540 	PangoFontDescription *d;
541 	void **r = user_data;
542 	fontsel_data *fd = r[2];
543 	int sz;
544 
545 	if (!fd->dpi || !fd->sysdpi) return; // Leave alone
546 	c = gtk_widget_get_pango_context(widget);
547 	if (!c) return;
548 	d = pango_context_get_font_description(c);
549 	sz = (pango_font_description_get_size(d) * fd->dpi) / fd->sysdpi;
550 	/* !!! 1st is used for font render, 2nd for GtkEntry size calculation;
551 	 * need to modify both the same way */
552 	pango_font_description_set_size(d, sz);
553 	pango_font_description_set_size(widget->style->font_desc, sz);
554 }
555 
556 #endif
557 #endif /* GTK+2&3 */
558 
fontsel_prepare(GtkWidget * widget,gpointer user_data)559 static void fontsel_prepare(GtkWidget *widget, gpointer user_data)
560 {
561 	char **ss = user_data;
562 	gtk_font_selection_set_font_name(GTK_FONT_SELECTION(widget), ss[0]);
563 	gtk_font_selection_set_preview_text(GTK_FONT_SELECTION(widget), ss[1]);
564 	ss[0] = ss[1] = NULL;
565 }
566 
fontsel(void ** r,void * v)567 static GtkWidget *fontsel(void **r, void *v)
568 {
569 	GtkWidget *widget = gtk_font_selection_new();
570 	GtkWidget *entry = gtk_font_selection_get_preview_entry(GTK_FONT_SELECTION(widget));
571 
572 	accept_ctrl_enter(entry);
573 	/* !!! Setting initial values fails if no toplevel */
574 	gtk_signal_connect(GTK_OBJECT(widget), "realize",
575 		GTK_SIGNAL_FUNC(fontsel_prepare), v);
576 #if GTK_MAJOR_VERSION == 3
577 	/* !!! Kill off animating size changes */
578 	css_restyle(entry, ".mtPaint_fontentry { transition-duration: 0; "
579 		"transition-delay: 0; }", "mtPaint_fontentry", NULL);
580 #endif
581 #if GTK_MAJOR_VERSION >= 2
582 	gtk_signal_connect(GTK_OBJECT(entry), FONT_SIGNAL,
583 		GTK_SIGNAL_FUNC(fontsel_style), r);
584 #endif
585 	return (widget);
586 }
587 
588 #define PAD_SIZE 2
589 
do_render_text(texteng_dd * td)590 static void do_render_text(texteng_dd *td)
591 {
592 	GtkWidget *widget = main_window;
593 #if GTK_MAJOR_VERSION == 3
594 	cairo_surface_t *text_pixmap;
595 	cairo_t *cr;
596 	cairo_matrix_t cm;
597 #else
598 	GdkPixmap *text_pixmap;
599 #endif
600 	unsigned char *buf;
601 	int width, height, have_rgb = 0;
602 
603 #if GTK_MAJOR_VERSION >= 2
604 
605 	static const PangoAlignment align[3] = {
606 		PANGO_ALIGN_LEFT, PANGO_ALIGN_CENTER, PANGO_ALIGN_RIGHT };
607 	PangoMatrix matrix = PANGO_MATRIX_INIT, *mp = NULL;
608 	PangoRectangle ink, rect;
609 	PangoContext *context;
610 	PangoLayout *layout;
611 	PangoFontDescription *font_desc;
612 	int tx, ty;
613 
614 
615 	context = gtk_widget_create_pango_context(widget);
616 	layout = pango_layout_new(context);
617 	font_desc = pango_font_description_from_string(td->font);
618 	if (td->dpi) pango_font_description_set_size(font_desc,
619 		(int)(pango_font_description_get_size(font_desc) *
620 		(td->dpi / window_dpi(widget))));
621 	pango_layout_set_font_description(layout, font_desc);
622 	pango_font_description_free(font_desc);
623 
624 	pango_layout_set_text(layout, td->text, -1);
625 	pango_layout_set_alignment(layout, align[td->align]);
626 
627 #if FULLPANGO
628 	if (td->spacing)
629 	{
630 		PangoAttrList *al = pango_attr_list_new();
631 		PangoAttribute *a = pango_attr_letter_spacing_new(
632 			(td->spacing * PANGO_SCALE) / 100);
633 		pango_attr_list_insert(al, a);
634 		pango_layout_set_attributes(layout, al);
635 		pango_attr_list_unref(al);
636 	}
637 	if (td->angle)
638 	{
639 		pango_matrix_rotate(&matrix, td->angle * 0.01);
640 		pango_context_set_matrix(context, &matrix);
641 		pango_layout_context_changed(layout);
642 		mp = &matrix;
643 	}
644 #endif
645 	pango_layout_get_extents(layout, &ink, &rect);
646 
647 	// Adjust height of ink rectangle to logical
648 	tx = ink.y + ink.height;
649 	ty = rect.y + rect.height;
650 	if (tx < ty) tx = ty;
651 	if (ink.y > rect.y) ink.y = rect.y;
652 	ink.height = tx - ink.y;
653 
654 	rotate_box(&rect, mp); // What gdk_draw_layout() uses
655 	rotate_box(&ink, mp); // What one should use
656 	tx = PAD_SIZE - ink.x + rect.x;
657 	ty = PAD_SIZE - ink.y + rect.y;
658 	width = ink.width + PAD_SIZE * 2;
659 	height = ink.height + PAD_SIZE * 2;
660 
661 #if GTK_MAJOR_VERSION == 3
662 	text_pixmap = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
663 
664 	cr = cairo_create(text_pixmap);
665 	cairo_translate(cr, tx - rect.x, ty - rect.y);
666 	if (mp)
667 	{
668 		cm.xx = mp->xx;
669 		cm.yx = mp->yx;
670 		cm.xy = mp->xy;
671 		cm.yy = mp->yy;
672 		cm.x0 = mp->x0;
673 		cm.y0 = mp->y0;
674 		cairo_transform(cr, &cm);
675 	}
676 	cairo_set_source_rgb(cr, 1, 1, 1);
677 	pango_cairo_show_layout(cr, layout);
678 	cairo_destroy(cr);
679 #else
680 	text_pixmap = gdk_pixmap_new(widget->window, width, height, -1);
681 
682 	gdk_draw_rectangle(text_pixmap, widget->style->black_gc, TRUE, 0, 0, width, height);
683 	gdk_draw_layout(text_pixmap, widget->style->white_gc, tx, ty, layout);
684 #endif
685 
686 	g_object_unref(layout);
687 	g_object_unref(context);
688 
689 #else /* #if GTK_MAJOR_VERSION == 1 */
690 
691 	GdkFont *t_font = gdk_font_load(td->font);
692 	int lbearing, rbearing, f_width, ascent, descent;
693 
694 
695 	gdk_string_extents(t_font, td->text,
696 		&lbearing, &rbearing, &f_width, &ascent, &descent);
697 
698 	width = rbearing - lbearing + PAD_SIZE * 2;
699 	height = ascent + descent + PAD_SIZE * 2;
700 
701 	text_pixmap = gdk_pixmap_new(widget->window, width, height, -1);
702 	gdk_draw_rectangle(text_pixmap, widget->style->black_gc, TRUE, 0, 0, width, height);
703 	gdk_draw_string(text_pixmap, t_font, widget->style->white_gc,
704 			PAD_SIZE - lbearing, ascent + PAD_SIZE, td->text);
705 
706 	gdk_font_unref(t_font);
707 
708 #endif
709 
710 	buf = malloc(width * height * 3);
711 	if (buf) have_rgb = !!wj_get_rgb_image(gtk_widget_get_window(widget),
712 		text_pixmap, buf, 0, 0, width, height);
713 	// REMOVE PIXMAP
714 #if GTK_MAJOR_VERSION == 3
715 	cairo_surface_destroy(text_pixmap);
716 #else
717 	gdk_pixmap_unref(text_pixmap);
718 #endif
719 
720 	memset(&td->ctx, 0, sizeof(td->ctx));
721 	if (!have_rgb) free(buf);
722 	else
723 	{
724 		td->ctx.xy[2] = width;
725 		td->ctx.xy[3] = height;
726 		td->ctx.rgb = buf;
727 	}
728 }
729 
730 //	Mouse handling
731 
732 typedef struct {
733 	void *slot[EV_SIZE];
734 	mouse_ext *mouse;
735 	int vport[4];
736 } mouse_den;
737 
do_evt_mouse(void ** slot,void * event,mouse_ext * mouse)738 static int do_evt_mouse(void **slot, void *event, mouse_ext *mouse)
739 {
740 	static void *ev_MOUSE = WBh(EV_MOUSE, 0);
741 	void **orig = origin_slot(slot);
742 	void **base = slot[0], **desc = slot[1];
743 	mouse_den den = { EVSLOT(ev_MOUSE, orig, event), mouse, { 0, 0, 0, 0 } };
744 	int op = GET_OP(orig);
745 
746 #if GTK_MAJOR_VERSION >= 2
747 	if ((((int)desc[0] & WB_OPMASK) >= op_EVT_XMOUSE0) && tablet_device)
748 	{
749 		gdouble pressure = 1.0;
750 		gdk_event_get_axis(event, GDK_AXIS_PRESSURE, &pressure);
751 		mouse->pressure = (int)(pressure * MAX_PRESSURE + 0.5);
752 	}
753 #endif
754 	if ((op == op_CANVASIMG) || (op == op_CANVASIMGB) || (op == op_CANVAS))
755 	{
756 		wjcanvas_get_vport(orig[0], den.vport);
757 		den.mouse->x += den.vport[0];
758 		den.mouse->y += den.vport[1];
759 	}
760 
761 	return (((evtxr_fn)desc[1])(GET_DDATA(base), base,
762 		(int)desc[0] & WB_OPMASK, den.slot, mouse));
763 }
764 
765 /* !!! After a drop, gtk_drag_end() sends to drag source a fake release event
766  * with coordinates (0, 0); remember to ignore them where necessary - WJ */
get_evt_mouse(GtkWidget * widget,GdkEventButton * event,gpointer user_data)767 static gboolean get_evt_mouse(GtkWidget *widget, GdkEventButton *event,
768 	gpointer user_data)
769 {
770 	mouse_ext mouse = {
771 		event->x, event->y, event->button,
772 		event->type == GDK_BUTTON_PRESS ? 1 :
773 		event->type == GDK_2BUTTON_PRESS ? 2 :
774 		event->type == GDK_3BUTTON_PRESS ? 3 :
775 		event->type == GDK_BUTTON_RELEASE ? -1 : 0,
776 		event->state, MAX_PRESSURE };
777 #if GTK_MAJOR_VERSION == 1
778 	if (GET_OP((void **)user_data) >= op_EVT_XMOUSE0)
779 		mouse.pressure = (int)(event->pressure * MAX_PRESSURE + 0.5);
780 #endif
781 	return (do_evt_mouse(user_data, event, &mouse));
782 }
783 
784 // This peculiar encoding is historically used throughout mtPaint
state_to_button(unsigned int state)785 static inline int state_to_button(unsigned int state)
786 {
787 	return ((state & _B13mask) == _B13mask ? 13 :
788 		state & _B1mask ? 1 :
789 		state & _B3mask ? 3 :
790 		state & _B2mask ? 2 : 0);
791 }
792 
get_evt_mmouse(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)793 static gboolean get_evt_mmouse(GtkWidget *widget, GdkEventMotion *event,
794 	gpointer user_data)
795 {
796 	mouse_ext mouse;
797 
798 	mouse.pressure = MAX_PRESSURE;
799 #if GTK_MAJOR_VERSION == 1
800 	if (GET_OP((void **)user_data) >= op_EVT_XMOUSE0)
801 	{
802 		gdouble pressure = event->pressure;
803 		if (event->is_hint) gdk_input_window_get_pointer(event->window,
804 			event->deviceid, NULL, NULL, &pressure, NULL, NULL, NULL);
805 		mouse.pressure = (int)(pressure * MAX_PRESSURE + 0.5);
806 	}
807 #endif
808 	while (TRUE)
809 	{
810 #if GTK_MAJOR_VERSION == 3
811 		if (event->is_hint)
812 		{
813 			gdk_event_request_motions(event);
814 			if (GET_OP((void **)user_data) < op_EVT_XMOUSE0)
815 			{
816 				gdk_window_get_device_position(event->window,
817 					event->device, &mouse.x, &mouse.y, &mouse.state);
818 				break;
819 			}
820 		}
821 #else /* #if GTK_MAJOR_VERSION <= 2 */
822 		if (!event->is_hint);
823 #if GTK_MAJOR_VERSION == 2
824 		else if (GET_OP((void **)user_data) >= op_EVT_XMOUSE0)
825 		{
826 			gdk_device_get_state(event->device, event->window,
827 				NULL, &mouse.state);
828 		}
829 #endif
830 		else
831 		{
832 			gdk_window_get_pointer(event->window,
833 				&mouse.x, &mouse.y, &mouse.state);
834 			break;
835 		}
836 #endif /* GTK+1&2 */
837 		mouse.x = event->x;
838 		mouse.y = event->y;
839 		mouse.state = event->state;
840 		break;
841 	}
842 
843 	mouse.button = state_to_button(mouse.state);
844 
845 	mouse.count = 0; // motion
846 
847 	return (do_evt_mouse(user_data, event, &mouse));
848 }
849 
enable_events(void ** slot,int op)850 static void enable_events(void **slot, int op)
851 {
852 #if GTK_MAJOR_VERSION == 3
853 	/* Let's be granular, here */
854 	GdkEventMask m;
855 	switch (op)
856 	{
857 	case op_EVT_KEY: m = GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK; break;
858 	/* Button release goes where button press went, b/c automatic grab */
859 	case op_EVT_MOUSE: case op_EVT_RMOUSE:
860 	case op_EVT_XMOUSE: case op_EVT_RXMOUSE:
861 		 m = GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK; break;
862 	case op_EVT_MMOUSE: case op_EVT_MXMOUSE: m = GDK_BUTTON_PRESS_MASK |
863 		GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK; break;
864 	case op_EVT_CROSS: m = GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK; break;
865 	case op_EVT_SCROLL: m = GDK_SCROLL_MASK; break;
866 	default: return;
867 	}
868 	gtk_widget_add_events(*slot, m);
869 #else /* #if GTK_MAJOR_VERSION <= 2 */
870 	if ((op >= op_EVT_MOUSE) && (op <= op_EVT_RXMOUSE))
871 	{
872 		if (op >= op_EVT_XMOUSE0) gtk_widget_set_extension_events(*slot,
873 			GDK_EXTENSION_EVENTS_CURSOR);
874 #if GTK_MAJOR_VERSION == 1
875 		if (GTK_WIDGET_NO_WINDOW(*slot)) return;
876 #endif
877 	}
878 	else if (op != op_EVT_CROSS) return; // Ignore op_EVT_KEY & op_EVT_SCROLL
879 	/* No granularity at all, but it was always this way, and it worked */
880 	gtk_widget_set_events(*slot, GDK_ALL_EVENTS_MASK);
881 #endif /* GTK+1&2 */
882 }
883 
884 // !!! With GCC inlining this, weird size fluctuations can happen. Or not.
add_mouse(void ** r,int op)885 static void add_mouse(void **r, int op)
886 {
887 	static const char *sn[3] = { "button_press_event",
888 		"motion_notify_event", "button_release_event" };
889 	void **slot = origin_slot(PREV_SLOT(r));
890 	int cw = op - op_EVT_MOUSE + (op >= op_EVT_XMOUSE0);
891 
892 	gtk_signal_connect(GTK_OBJECT(*slot), sn[cw & 3],
893 		cw & 1 ? GTK_SIGNAL_FUNC(get_evt_mmouse) :
894 		GTK_SIGNAL_FUNC(get_evt_mouse), r);
895 	enable_events(slot, op);
896 }
897 
get_evt_cross(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)898 static gboolean get_evt_cross(GtkWidget *widget, GdkEventCrossing *event,
899 	gpointer user_data)
900 {
901 	void **slot, **base, **desc;
902 
903 	/* Skip grab/ungrab related events */
904 	if (event->mode != GDK_CROSSING_NORMAL) return (FALSE);
905 
906 	slot = user_data; base = slot[0]; desc = slot[1];
907 	((evtx_fn)desc[1])(GET_DDATA(base), base, (int)desc[0] & WB_OPMASK, slot,
908 		(void *)(event->type == GDK_ENTER_NOTIFY));
909 
910 	return (FALSE); // let it propagate
911 }
912 
913 #if GTK_MAJOR_VERSION >= 2
914 
get_evt_scroll(GtkWidget * widget,GdkEventScroll * event,gpointer user_data)915 static gboolean get_evt_scroll(GtkWidget *widget, GdkEventScroll *event,
916 	gpointer user_data)
917 {
918 	void **slot = user_data, **base = slot[0], **desc = slot[1];
919 	scroll_ext scroll = { event->direction == GDK_SCROLL_LEFT ? -1 :
920 		event->direction == GDK_SCROLL_RIGHT ? 1 : 0,
921 		event->direction == GDK_SCROLL_UP ? -1 :
922 		event->direction == GDK_SCROLL_DOWN ? 1 : 0,
923 		event->state };
924 
925 	((evtx_fn)desc[1])(GET_DDATA(base), base,
926 		(int)desc[0] & WB_OPMASK, slot, &scroll);
927 
928 	if (!(scroll.xscroll | scroll.yscroll)) return (TRUE);
929 	event->direction = scroll.xscroll < 0 ? GDK_SCROLL_LEFT :
930 		scroll.xscroll > 0 ? GDK_SCROLL_RIGHT :
931 		scroll.yscroll < 0 ? GDK_SCROLL_UP :
932 		/* scroll.yscroll > 0 ? */ GDK_SCROLL_DOWN;
933 	return (FALSE);
934 }
935 #endif
936 
937 //	Drag handling
938 
939 typedef struct {
940 	int n;
941 	clipform_dd *src;
942 	GtkTargetList *targets;
943 	GtkTargetEntry ent[1];
944 } clipform_data;
945 
946 typedef struct {
947 	int x, y, may_drag;
948 	int color;
949 	void **r;
950 	clipform_data *cd;
951 } drag_ctx;
952 
953 typedef struct {
954 	GdkDragContext *drag_context;
955 	GtkSelectionData *data;
956 	guint info;
957 	guint time;
958 } drag_sel;
959 
960 typedef struct {
961 	void *slot[EV_SIZE];
962 	drag_sel *ds;
963 	drag_ctx *dc;
964 	drag_ext d;
965 } drag_den;
966 
drag_event(drag_sel * ds,drag_ctx * dc)967 static int drag_event(drag_sel *ds, drag_ctx *dc)
968 {
969 	static void *ev_DRAGFROM = WBh(EV_DRAGFROM, 0);
970 	void **slot = dc->r, **orig = origin_slot(slot);
971 	void **base = slot[0], **desc = slot[1];
972 	drag_den den = { EVSLOT(ev_DRAGFROM, orig, den.slot), ds, dc };
973 
974 	/* While technically, drag starts at current position, I use the saved
975 	 * one - where user had clicked to begin drag - WJ */
976 	// !!! Struct not stable yet, so init fields one by one
977 	memset(&den.d, 0, sizeof(den.d));
978 	den.d.x = dc->x;
979 	den.d.y = dc->y;
980 	den.d.format = ds ? dc->cd->src + ds->info : NULL;
981 
982 	return (((evtxr_fn)desc[1])(GET_DDATA(base), base,
983 		(int)desc[0] & WB_OPMASK, den.slot, &den.d));
984 }
985 
986 #define RGB_DND_W 48
987 #define RGB_DND_H 32
988 
set_drag_icon(GdkDragContext * context,GtkWidget * src,int rgb)989 static void set_drag_icon(GdkDragContext *context, GtkWidget *src, int rgb)
990 {
991 #if GTK_MAJOR_VERSION == 3
992 	GdkPixbuf *p = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, RGB_DND_W, RGB_DND_H);
993 	gdk_pixbuf_fill(p, ((unsigned)rgb << 8) + 0xFF);
994 	gtk_drag_set_icon_pixbuf(context, p, -2, -2);
995 	g_object_unref(p);
996 #else /* #if GTK_MAJOR_VERSION <= 2 */
997 	GdkGCValues sv;
998 	GdkPixmap *swatch;
999 
1000 	swatch = gdk_pixmap_new(src->window, RGB_DND_W, RGB_DND_H, -1);
1001 	gdk_gc_get_values(src->style->black_gc, &sv);
1002 	gdk_rgb_gc_set_foreground(src->style->black_gc, rgb);
1003 	gdk_draw_rectangle(swatch, src->style->black_gc, TRUE, 0, 0,
1004 		RGB_DND_W, RGB_DND_H);
1005 	gdk_gc_set_foreground(src->style->black_gc, &sv.foreground);
1006 	gtk_drag_set_icon_pixmap(context, gtk_widget_get_colormap(src),
1007 		swatch, NULL, -2, -2);
1008 	gdk_pixmap_unref(swatch);
1009 #endif /* GTK+1&2 */
1010 }
1011 
1012 /* !!! In GTK+3 icon must be set in "drag_begin", setting it in try_start_drag()
1013  * somehow only sets it for not-valid-target case - WJ */
begin_drag(GtkWidget * widget,GdkDragContext * context,gpointer user_data)1014 static void begin_drag(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
1015 {
1016 	drag_ctx *dc = user_data;
1017 	if (dc->color >= 0) set_drag_icon(context, widget, dc->color);
1018 }
1019 
try_start_drag(GtkWidget * widget,GdkEvent * event,gpointer user_data)1020 static int try_start_drag(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1021 {
1022 	drag_ctx *dc = user_data;
1023 	int rx, ry;
1024 	GdkModifierType state;
1025 
1026 	if (event->type == GDK_BUTTON_PRESS)
1027 	{
1028 		if (event->button.button == 1) // Only left button inits drag
1029 		{
1030 			dc->x = event->button.x;
1031 			dc->y = event->button.y;
1032 			dc->may_drag = TRUE;
1033 		}
1034 	}
1035 	else if (event->type == GDK_BUTTON_RELEASE)
1036 	{
1037 		if (event->button.button == 1) dc->may_drag = FALSE;
1038 	}
1039 	else if (event->type == GDK_MOTION_NOTIFY)
1040 	{
1041 		if (event->motion.is_hint)
1042 #if GTK_MAJOR_VERSION == 3
1043 			gdk_window_get_device_position(event->motion.window,
1044 				event->motion.device, &rx, &ry, &state);
1045 #else /* if GTK_MAJOR_VERSION <= 2 */
1046 			gdk_window_get_pointer(event->motion.window, &rx, &ry, &state);
1047 #endif
1048 		else
1049 		{
1050 			rx = event->motion.x;
1051 			ry = event->motion.y;
1052 			state = event->motion.state;
1053 		}
1054 		/* May init drag */
1055 		if (state & GDK_BUTTON1_MASK)
1056 		{
1057 			/* No dragging without clicking on the widget first */
1058 			if (dc->may_drag &&
1059 #if GTK_MAJOR_VERSION == 1
1060 				((abs(rx - dc->x) > 3) ||
1061 				(abs(ry - dc->y) > 3))
1062 #else /* if GTK_MAJOR_VERSION >= 2 */
1063 				gtk_drag_check_threshold(widget,
1064 					dc->x, dc->y, rx, ry)
1065 #endif
1066 			) /* Initiate drag */
1067 			{
1068 				dc->may_drag = FALSE;
1069 				/* Call handler so it can decide if it wants
1070 				 * this drag, and maybe set icon color */
1071 				dc->color = -1;
1072 				if (!drag_event(NULL, dc)) return (TRUE); // no drag
1073 #if GTK_MAJOR_VERSION == 3
1074 				gtk_drag_begin_with_coordinates(widget,
1075 					dc->cd->targets, GDK_ACTION_COPY |
1076 					GDK_ACTION_MOVE, 1, event, -1, -1);
1077 #else /* if GTK_MAJOR_VERSION <= 2 */
1078 				gtk_drag_begin(widget, dc->cd->targets,
1079 					GDK_ACTION_COPY | GDK_ACTION_MOVE, 1, event);
1080 #endif
1081 				return (TRUE);
1082 			}
1083 		}
1084 		else dc->may_drag = FALSE; // Release events can be lost
1085 	}
1086 	return (FALSE);
1087 }
1088 
get_evt_drag(GtkWidget * widget,GdkDragContext * drag_context,GtkSelectionData * data,guint info,guint time,gpointer user_data)1089 static void get_evt_drag(GtkWidget *widget, GdkDragContext *drag_context,
1090 	GtkSelectionData *data, guint info, guint time, gpointer user_data)
1091 {
1092 	drag_sel ds = { drag_context, data, info, time };
1093 	drag_event(&ds, user_data);
1094 }
1095 
1096 static void fcimage_rxy(void **slot, int *xy);
1097 
get_evt_drop(GtkWidget * widget,GdkDragContext * drag_context,gint x,gint y,GtkSelectionData * data,guint info,guint time,gpointer user_data)1098 static void get_evt_drop(GtkWidget *widget, GdkDragContext *drag_context,
1099 	gint x,	gint y,	GtkSelectionData *data,	guint info, guint time,
1100 	gpointer user_data)
1101 {
1102 	void **dd = user_data, **orig = origin_slot(dd);
1103 	void **slot, **base, **desc;
1104 	clipform_data *cd = *dd; // from DRAGDROP slot
1105 	int res, op = GET_OP(orig), xy[2] = { x, y };
1106 	drag_ext dx;
1107 
1108 #if GTK_MAJOR_VERSION == 1
1109 	/* GTK+1 provides window-relative coordinates, not widget-relative */
1110 	if (GTK_WIDGET_NO_WINDOW(*orig))
1111 	{
1112 		xy[0] -= GTK_WIDGET(*orig)->allocation.x;
1113 		xy[1] -= GTK_WIDGET(*orig)->allocation.y;
1114 	}
1115 #endif
1116 	/* Map widget-relative coordinates to inner window */
1117 	if (op == op_FCIMAGEP) fcimage_rxy(orig, xy);
1118 
1119 	// !!! Struct not stable yet, so init fields one by one
1120 	memset(&dx, 0, sizeof(dx));
1121 	dx.x = xy[0];
1122 	dx.y = xy[1];
1123 	dx.format = cd->src + info;
1124 	dx.data = (void *)gtk_selection_data_get_data(data);
1125 	dx.len = gtk_selection_data_get_length(data);
1126 
1127 	/* Selection data format isn't checked because it's how GTK+2 does it,
1128 	 * reportedly to ignore a bug in (some versions of) KDE - WJ */
1129 	res = dx.format->size ? dx.len == dx.format->size : dx.len >= 0;
1130 
1131 	slot = SLOT_N(dd, 2); // from DRAGDROP to its EVT_DROP
1132 	base = slot[0]; desc = slot[1];
1133 	((evtx_fn)desc[1])(GET_DDATA(base), base,
1134 		(int)desc[0] & WB_OPMASK, slot, &dx);
1135 
1136 	if (GET_DESCV(dd, 2)) // Accept move as a copy (disallow deleting source)
1137 		gtk_drag_finish(drag_context, res, FALSE, time);
1138 }
1139 
tried_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer user_data)1140 static gboolean tried_drop(GtkWidget *widget, GdkDragContext *context,
1141 	gint x, gint y, guint time, gpointer user_data)
1142 {
1143 	GdkAtom target;
1144 	/* Check if drop could provide a supported format */
1145 #if GTK_MAJOR_VERSION == 1
1146 	clipform_data *cd = user_data;
1147 	GList *dest, *src;
1148 	gpointer tp;
1149 
1150 	for (dest = cd->targets->list; dest; dest = dest->next)
1151 	{
1152 		target = ((GtkTargetPair *)dest->data)->target;
1153 		tp = GUINT_TO_POINTER(target);
1154 		for (src = context->targets; src && (src->data != tp); src = src->next);
1155 		if (src) break;
1156 	}
1157 	if (!dest) return (FALSE);
1158 #else /* if GTK_MAJOR_VERSION >= 2 */
1159 	target = gtk_drag_dest_find_target(widget, context, NULL);
1160 	if (target == GDK_NONE) return (FALSE);
1161 #endif
1162 
1163 	/* Trigger "drag_data_received" event */
1164 	gtk_drag_get_data(widget, context, target, time);
1165 	return (TRUE);
1166 }
1167 
dragdrop(void ** r)1168 void *dragdrop(void **r)
1169 {
1170 	void *v = r[0], **pp = r[1], **slot = origin_slot(PREV_SLOT(r));
1171 	clipform_data *cd = **(void ***)v;
1172 
1173 	if (pp[4]) // Have drag handler
1174 	{
1175 		drag_ctx *dc = r[2];
1176 		dc->r = NEXT_SLOT(r);
1177 		dc->cd = cd;
1178 		gtk_signal_connect(GTK_OBJECT(*slot), "button_press_event",
1179 			GTK_SIGNAL_FUNC(try_start_drag), dc);
1180 		gtk_signal_connect(GTK_OBJECT(*slot), "motion_notify_event",
1181 			GTK_SIGNAL_FUNC(try_start_drag), dc);
1182 		gtk_signal_connect(GTK_OBJECT(*slot), "button_release_event",
1183 			GTK_SIGNAL_FUNC(try_start_drag), dc);
1184 		gtk_signal_connect(GTK_OBJECT(*slot), "drag_begin",
1185 			GTK_SIGNAL_FUNC(begin_drag), dc);
1186 		gtk_signal_connect(GTK_OBJECT(*slot), "drag_data_get",
1187 			GTK_SIGNAL_FUNC(get_evt_drag), dc);
1188 	}
1189 	if (pp[6]) // Have drop handler
1190 	{
1191 		int dmode = GTK_DEST_DEFAULT_HIGHLIGHT |
1192 			GTK_DEST_DEFAULT_MOTION;
1193 		if (!pp[2]) dmode |= GTK_DEST_DEFAULT_DROP;
1194 		else gtk_signal_connect(GTK_OBJECT(*slot), "drag_drop",
1195 			GTK_SIGNAL_FUNC(tried_drop), cd);
1196 
1197 		gtk_drag_dest_set(*slot, dmode, cd->ent, cd->n, pp[2] ?
1198 			GDK_ACTION_COPY | GDK_ACTION_MOVE : GDK_ACTION_COPY);
1199 		gtk_signal_connect(GTK_OBJECT(*slot), "drag_data_received",
1200 			GTK_SIGNAL_FUNC(get_evt_drop), r);
1201 	}
1202 	return (cd); // For drop handler
1203 }
1204 
1205 //	Clipboard handling
1206 
1207 typedef struct {
1208 	void *slot[EV_SIZE];
1209 	GtkSelectionData *data;
1210 	guint info;
1211 	clipform_data *cd;
1212 	copy_ext c;
1213 } copy_den;
1214 
clip_evt(GtkSelectionData * sel,guint info,void ** slot)1215 static void clip_evt(GtkSelectionData *sel, guint info, void **slot)
1216 {
1217 	static void *ev_COPY = WBh(EV_COPY, 0);
1218 	void **base, **desc;
1219 	clipform_data *cd = slot[0];
1220 	copy_den den = { EVSLOT(ev_COPY, slot, den.slot), sel, info, cd,
1221 		{ sel ? cd->src + info : NULL, NULL, 0 } };
1222 
1223 	slot = NEXT_SLOT(slot); // from CLIPBOARD to EVT_COPY
1224 	base = slot[0]; desc = slot[1];
1225 	((evtx_fn)desc[1])(GET_DDATA(base), base,
1226 		(int)desc[0] & WB_OPMASK, den.slot, &den.c);
1227 }
1228 
paste_evt(GtkSelectionData * sel,clipform_dd * format,void ** slot)1229 static int paste_evt(GtkSelectionData *sel, clipform_dd *format, void **slot)
1230 {
1231 	void **base = slot[0], **desc = slot[1];
1232 	copy_ext c = { format, (void *)gtk_selection_data_get_data(sel),
1233 		gtk_selection_data_get_length(sel) };
1234 
1235 	return (((evtxr_fn)desc[1])(GET_DDATA(base), base,
1236 		(int)desc[0] & WB_OPMASK, slot, &c));
1237 }
1238 
clip_format(GtkSelectionData * sel,clipform_data * cd)1239 static clipform_dd *clip_format(GtkSelectionData *sel, clipform_data *cd)
1240 {
1241 	GdkAtom *targets;
1242 	int i, n = gtk_selection_data_get_length(sel) / sizeof(GdkAtom);
1243 
1244 	if ((n > 0) && (gtk_selection_data_get_format(sel) == 32) &&
1245 		(gtk_selection_data_get_data_type(sel) == GDK_SELECTION_TYPE_ATOM))
1246 	{
1247 #if GTK_MAJOR_VERSION == 3
1248 		guint res, res0 = G_MAXUINT;
1249 
1250 		targets = (GdkAtom *)gtk_selection_data_get_data(sel);
1251 #if 0 /* #ifdef GDK_WINDOWING_QUARTZ */
1252 		/* !!! For debugging */
1253 		g_print("%d targets:\n", n);
1254 		for (i = 0; i < n; i++) g_print("%s\n", gdk_atom_name(targets[i]));
1255 #endif
1256 		/* Need to scan for each target, to return the earliest slot in
1257 		 * the array in case more than one match */
1258 		for (i = 0; i < n; i++)
1259 			if (gtk_target_list_find(cd->targets, targets[i], &res) &&
1260 				(res < res0)) res0 = res;
1261 		// Return the matching format
1262 		if (res0 < G_MAXUINT) return (cd->src + res0);
1263 #else /* if GTK_MAJOR_VERSION <= 2 */
1264 		GList *dest;
1265 		GdkAtom target;
1266 
1267 		targets = (GdkAtom *)gtk_selection_data_get_data(sel);
1268 		for (dest = cd->targets->list; dest; dest = dest->next)
1269 		{
1270 			target = ((GtkTargetPair *)dest->data)->target;
1271 			for (i = 0; (i < n) && (targets[i] != target); i++);
1272 			if (i < n) break;
1273 		}
1274 		// Return the matching format
1275 		if (dest) return (cd->src + ((GtkTargetPair *)dest->data)->info);
1276 #endif
1277 	}
1278 	return (NULL);
1279 }
1280 
1281 #if GTK_MAJOR_VERSION == 1
1282 
1283 #define CLIPMASK 3 /* 2 clipboards */
1284 
1285 enum {
1286 	TEXT_STRING = 0,
1287 	TEXT_TEXT,
1288 	TEXT_COMPOUND,
1289 	TEXT_CNT
1290 };
1291 
1292 static GtkTargetEntry text_targets[] = {
1293 	{ "STRING", 0, TEXT_STRING },
1294 	{ "TEXT", 0, TEXT_TEXT },
1295 	{ "COMPOUND_TEXT", 0, TEXT_COMPOUND }
1296 };
1297 static GtkTargetList *text_tlist;
1298 
1299 typedef struct {
1300 	char *what;
1301 	int where;
1302 } text_offer;
1303 static text_offer text_on_offer[2];
1304 
clear_text_offer(int nw)1305 static void clear_text_offer(int nw)
1306 {
1307 	int i;
1308 
1309 	for (i = 0; i < 2; i++)
1310 	{
1311 		if (text_on_offer[i].where &= ~nw) continue;
1312 		free(text_on_offer[i].what);
1313 		text_on_offer[i].what = NULL;
1314 	}
1315 }
1316 
selection_callback(GtkWidget * widget,GtkSelectionData * sel,guint time,gpointer user_data)1317 static void selection_callback(GtkWidget *widget, GtkSelectionData *sel,
1318 	guint time, gpointer user_data)
1319 {
1320 	void **res = user_data;
1321 	if (!res[1]) res[1] = clip_format(sel, res[0]); // check
1322 	else if ((sel->length < 0) || !paste_evt(sel, res[1], res[0])) // paste
1323 		res[1] = NULL; // fail
1324 	res[0] = NULL; // done
1325 }
1326 
process_clipboard(void ** slot)1327 static int process_clipboard(void **slot)
1328 {
1329 	clipform_data *cd = slot[0];
1330 	void **desc = slot[1];
1331 	void *res[2] = { NULL, NULL };
1332 	clipform_dd *format;
1333 	GdkAtom sel;
1334 	guint sig = 0;
1335 	int which, nw = (int)desc[2] & CLIPMASK;
1336 
1337 	for (which = 0; (1 << which) <= nw; which++)
1338 	{
1339 		if (!((1 << which) & nw)) continue;
1340 		// If we're who put data there
1341 // !!! No check if same program but another clipboard - no need for now
1342 		if (internal_clipboard(which)) continue;
1343 		sel = gdk_atom_intern(which ? "PRIMARY" : "CLIPBOARD", FALSE);
1344 
1345 		res[0] = cd; res[1] = NULL;
1346 		if (!sig) sig = gtk_signal_connect(GTK_OBJECT(main_window),
1347 			"selection_received",
1348 			GTK_SIGNAL_FUNC(selection_callback), &res);
1349 		/* First, deciding on format... */
1350 		gtk_selection_convert(main_window, sel,
1351 			gdk_atom_intern("TARGETS", FALSE), GDK_CURRENT_TIME);
1352 		while (res[0]) gtk_main_iteration();
1353 		if (!(format = res[1])) continue; // no luck
1354 		/* ...then, requesting the format */
1355 		res[0] = SLOT_N(slot, 2); // from CLIPBOARD to EVT_PASTE
1356 		gtk_selection_convert(main_window, sel,
1357 			gdk_atom_intern(format->target, FALSE), GDK_CURRENT_TIME);
1358 		while (res[0]) gtk_main_iteration();
1359 		if (res[1]) break; // success
1360 	}
1361 	if (sig) gtk_signal_disconnect(GTK_OBJECT(main_window), sig);
1362 
1363 	return (!!res[1]);
1364 }
1365 
selection_get_callback(GtkWidget * widget,GtkSelectionData * sel,guint info,guint time,gpointer user_data)1366 static void selection_get_callback(GtkWidget *widget, GtkSelectionData *sel,
1367 	guint info, guint time, gpointer user_data)
1368 {
1369 	void **slot = g_dataset_get_data(main_window,
1370 		gdk_atom_name(sel->selection));
1371 	/* Text clipboard is handled right here */
1372 	if (slot == (void *)text_on_offer)
1373 	{
1374 		char *s;
1375 		int i, which;
1376 
1377 		which = sel->selection == gdk_atom_intern("PRIMARY", FALSE) ? 2 : 1;
1378 		for (i = 0; i < 2; i++) if (text_on_offer[i].where & which) break;
1379 		if (i > 1) return; // No text for this clipboard
1380 		s = text_on_offer[i].what;
1381 		if (!s) return; // Paranoia
1382 		if (info == TEXT_STRING)
1383 			gtk_selection_data_set(sel, sel->target, 8, s, strlen(s));
1384 		else if ((info == TEXT_TEXT) || (info == TEXT_COMPOUND))
1385 		{
1386 			guchar *ns;
1387 			GdkAtom target;
1388 			gint format, len;
1389 			gdk_string_to_compound_text(s, &target, &format, &ns, &len);
1390 			gtk_selection_data_set(sel, target, format, ns, len);
1391 			gdk_free_compound_text(ns);
1392 		}
1393 	}
1394 	/* Other kinds get handled outside */
1395 	else if (slot) clip_evt(sel, info, slot);
1396 }
1397 
selection_clear_callback(GtkWidget * widget,GdkEventSelection * event,gpointer user_data)1398 static void selection_clear_callback(GtkWidget *widget, GdkEventSelection *event,
1399 	gpointer user_data)
1400 {
1401 	void **slot = g_dataset_get_data(main_window,
1402 		gdk_atom_name(event->selection));
1403 	if (slot == (void *)text_on_offer) clear_text_offer(
1404 		event->selection == gdk_atom_intern("PRIMARY", FALSE) ? 2 : 1);
1405 	else if (slot) clip_evt(NULL, 0, slot);
1406 }
1407 
1408 // !!! GTK+ 1.2 internal type (gtk/gtkselection.c)
1409 typedef struct {
1410 	GdkAtom selection;
1411 	GtkTargetList *list;
1412 } GtkSelectionTargetList;
1413 
offer_clipboard(void ** slot,int text)1414 static int offer_clipboard(void **slot, int text)
1415 {
1416 	static int connected;
1417 	void **desc = slot[1];
1418 	clipform_data *cd = slot[0];
1419 	GtkTargetEntry *targets = cd->ent;
1420 	gpointer info = slot;
1421 	GtkSelectionTargetList *slist;
1422 	GList *list, *tmp;
1423 	GdkAtom sel;
1424 	int which, res = FALSE, nw = (int)desc[2] & CLIPMASK, ntargets = cd->n;
1425 
1426 
1427 	if (text) targets = text_targets , ntargets = TEXT_CNT , info = text_on_offer;
1428 	for (which = 0; (1 << which) <= nw; which++)
1429 	{
1430 		if (!((1 << which) & nw)) continue;
1431 		sel = gdk_atom_intern(which ? "PRIMARY" : "CLIPBOARD", FALSE);
1432 		if (!gtk_selection_owner_set(main_window, sel, GDK_CURRENT_TIME))
1433 			continue;
1434 
1435 		/* Don't have gtk_selection_clear_targets() in GTK+1 - have to
1436 		 * reimplement */
1437 		list = gtk_object_get_data(GTK_OBJECT(main_window),
1438 			"gtk-selection-handlers");
1439 		for (tmp = list; tmp; tmp = tmp->next)
1440 		{
1441 			if ((slist = tmp->data)->selection != sel) continue;
1442 			list = g_list_remove_link(list, tmp);
1443 			gtk_target_list_unref(slist->list);
1444 			g_free(slist);
1445 			break;
1446 		}
1447 		gtk_object_set_data(GTK_OBJECT(main_window),
1448 			"gtk-selection-handlers", list);
1449 
1450 		// !!! Have to resort to this to allow for multiple X clipboards
1451 		g_dataset_set_data(main_window, which ? "PRIMARY" : "CLIPBOARD", info);
1452 		if (!connected)
1453 		{
1454 			gtk_signal_connect(GTK_OBJECT(main_window), "selection_get",
1455 				GTK_SIGNAL_FUNC(selection_get_callback), NULL);
1456 			gtk_signal_connect(GTK_OBJECT(main_window), "selection_clear_event",
1457 				GTK_SIGNAL_FUNC(selection_clear_callback), NULL);
1458 			connected = TRUE;
1459 		}
1460 
1461 		gtk_selection_add_targets(main_window, sel, targets, ntargets);
1462 		res = TRUE;
1463 	}
1464 
1465 	return (res);
1466 }
1467 
offer_text(void ** slot,char * s)1468 static void offer_text(void **slot, char *s)
1469 {
1470 	void **desc = slot[1];
1471 	int i, nw = (int)desc[2] & CLIPMASK;
1472 
1473 	clear_text_offer(nw);
1474 	i = !!text_on_offer[0].what;
1475 	text_on_offer[i].what = strdup(s);
1476 	text_on_offer[i].where = nw;
1477 
1478 	if (!text_tlist) text_tlist = gtk_target_list_new(text_targets, TEXT_CNT);
1479 
1480 	offer_clipboard(slot, TRUE);
1481 }
1482 
1483 #else /* if GTK_MAJOR_VERSION >= 2 */
1484 
1485 #ifdef GDK_WINDOWING_X11
1486 #define CLIPMASK 3 /* 2 clipboards */
1487 #else
1488 #define CLIPMASK 1 /* 1 clipboard */
1489 #endif
1490 
1491 /* While GTK+2 allows for synchronous clipboard handling, it's implemented
1492  * through copying the entire clipboard data - and when the data might be
1493  * a huge image which mtPaint is bound to allocate *yet again*, this would be
1494  * asking for trouble - WJ */
1495 
clip_paste(GtkClipboard * clipboard,GtkSelectionData * sel,gpointer user_data)1496 static void clip_paste(GtkClipboard *clipboard, GtkSelectionData *sel,
1497 	gpointer user_data)
1498 {
1499 	void **res = user_data;
1500 	if ((gtk_selection_data_get_length(sel) < 0) ||
1501 		!paste_evt(sel, res[1], res[0])) res[1] = NULL; // fail
1502 	res[0] = NULL; // done
1503 }
1504 
clip_check(GtkClipboard * clipboard,GtkSelectionData * sel,gpointer user_data)1505 static void clip_check(GtkClipboard *clipboard, GtkSelectionData *sel,
1506 	gpointer user_data)
1507 {
1508 	void **res = user_data;
1509 	res[1] = clip_format(sel, res[0]);
1510 	res[0] = NULL; // done
1511 }
1512 
process_clipboard(void ** slot)1513 static int process_clipboard(void **slot)
1514 {
1515 	GtkClipboard *clip;
1516 	clipform_data *cd = slot[0];
1517 	void **desc = slot[1];
1518 	int which, nw = (int)desc[2] & CLIPMASK;
1519 
1520 	for (which = 0; (1 << which) <= nw; which++)
1521 	{
1522 		if (!((1 << which) & nw)) continue;
1523 		// If we're who put data there
1524 // !!! No check if same program but another clipboard - no need for now
1525 		if (internal_clipboard(which)) continue;
1526 		clip = gtk_clipboard_get(which ? GDK_SELECTION_PRIMARY :
1527 			GDK_SELECTION_CLIPBOARD);
1528 		{
1529 			void *res[] = { cd, NULL };
1530 			clipform_dd *format;
1531 			/* First, deciding on format... */
1532 			gtk_clipboard_request_contents(clip,
1533 				gdk_atom_intern("TARGETS", FALSE),
1534 				clip_check, res);
1535 			while (res[0]) gtk_main_iteration();
1536 			if (!(format = res[1])) continue; // no luck
1537 			/* ...then, requesting the format */
1538 			res[0] = SLOT_N(slot, 2); // from CLIPBOARD to EVT_PASTE
1539 			gtk_clipboard_request_contents(clip,
1540 				gdk_atom_intern(format->target, FALSE),
1541 				clip_paste, res);
1542 			while (res[0]) gtk_main_iteration();
1543 			if (res[1]) return (TRUE); // success
1544 		}
1545 	}
1546 	return (FALSE);
1547 }
1548 
1549 #ifdef GDK_WINDOWING_QUARTZ
1550 
1551 /* GTK+/Quartz has a dummy stub for gdk_selection_owner_get(), so needs a
1552  * workaround to have a working internal_clipboard() */
1553 
1554 static void* slot_on_offer;
1555 
clip_copy(GtkClipboard * clipboard,GtkSelectionData * sel,guint info,gpointer user_data)1556 static void clip_copy(GtkClipboard *clipboard, GtkSelectionData *sel, guint info,
1557 	gpointer user_data)
1558 {
1559 	clip_evt(sel, info, slot_on_offer);
1560 }
1561 
clip_clear(GtkClipboard * clipboard,gpointer user_data)1562 static void clip_clear(GtkClipboard *clipboard, gpointer user_data)
1563 {
1564 	clip_evt(NULL, 0, slot_on_offer);
1565 }
1566 
offer_clipboard(void ** slot,int unused)1567 static int offer_clipboard(void **slot, int unused)
1568 {
1569 	void **desc = slot[1];
1570 	clipform_data *cd = slot[0];
1571 	int i, res = FALSE;
1572 
1573 	if ((int)desc[2] & CLIPMASK) // Paranoia
1574 	// Two attempts, for GTK+ function can fail for strange reasons
1575 	for (i = 0; i < 2; i++)
1576 	{
1577 		if (!gtk_clipboard_set_with_owner(gtk_clipboard_get(
1578 			GDK_SELECTION_CLIPBOARD), cd->ent, cd->n,
1579 			clip_copy, clip_clear, G_OBJECT(main_window))) continue;
1580 		slot_on_offer = slot;
1581 		res = TRUE;
1582 		break;
1583 	}
1584 	return (res);
1585 }
1586 
1587 #else /* Anything but Quartz */
1588 
clip_copy(GtkClipboard * clipboard,GtkSelectionData * sel,guint info,gpointer user_data)1589 static void clip_copy(GtkClipboard *clipboard, GtkSelectionData *sel, guint info,
1590 	gpointer user_data)
1591 {
1592 	clip_evt(sel, info, user_data);
1593 }
1594 
clip_clear(GtkClipboard * clipboard,gpointer user_data)1595 static void clip_clear(GtkClipboard *clipboard, gpointer user_data)
1596 {
1597 	clip_evt(NULL, 0, user_data);
1598 }
1599 
offer_clipboard(void ** slot,int unused)1600 static int offer_clipboard(void **slot, int unused)
1601 {
1602 	void **desc = slot[1];
1603 	clipform_data *cd = slot[0];
1604 	int i, which, res = FALSE, nw = (int)desc[2] & CLIPMASK;
1605 
1606 	for (which = 0; (1 << which) <= nw; which++)
1607 	{
1608 		if (!((1 << which) & nw)) continue;
1609 		// Two attempts, for GTK+2 function can fail for strange reasons
1610 		for (i = 0; i < 2; i++)
1611 		{
1612 			if (!gtk_clipboard_set_with_data(gtk_clipboard_get(
1613 				which ? GDK_SELECTION_PRIMARY :
1614 				GDK_SELECTION_CLIPBOARD), cd->ent, cd->n,
1615 				clip_copy, clip_clear, slot)) continue;
1616 			res = TRUE;
1617 			break;
1618 		}
1619 	}
1620 	return (res);
1621 }
1622 
1623 #endif
1624 
offer_text(void ** slot,char * s)1625 static void offer_text(void **slot, char *s)
1626 {
1627 	void **desc = slot[1];
1628 	int which, nw = (int)desc[2] & CLIPMASK;
1629 
1630 	for (which = 0; (1 << which) <= nw; which++)
1631 	{
1632 		if (!((1 << which) & nw)) continue;
1633 		gtk_clipboard_set_text(gtk_clipboard_get(which ?
1634 			GDK_SELECTION_PRIMARY : GDK_SELECTION_CLIPBOARD), s, -1);
1635 	}
1636 }
1637 
1638 #endif
1639 
1640 #undef CLIPMASK
1641 
1642 //	Keynames handling
1643 
1644 #if GTK_MAJOR_VERSION == 1
1645 #include <ctype.h>
1646 #endif
1647 
get_keyname(char * buf,int buflen,guint key,guint state,int ui)1648 static void get_keyname(char *buf, int buflen, guint key, guint state, int ui)
1649 {
1650 	char tbuf[128], *name, *m1, *m2, *m3, *sp;
1651 	guint u;
1652 
1653 	if (!ui) /* For inifile */
1654 	{
1655 		m1 = state & _Cmask ? "C" : "";
1656 		m2 = state & _Smask ? "S" : "";
1657 		m3 = state & _Amask ? "A" : "";
1658 		sp = "_";
1659 		name = gdk_keyval_name(key);
1660 	}
1661 	else /* For display */
1662 	{
1663 		m1 = state & _Smask ? "Shift+" : "";
1664 		m2 = state & _Cmask ? "Ctrl+" : "";
1665 		m3 = state & _Amask ? "Alt+" : "";
1666 		sp = "";
1667 		name = tbuf;
1668 #if GTK_MAJOR_VERSION == 1
1669 		u = key;
1670 #else /* #if GTK_MAJOR_VERSION >= 2 */
1671 		u = gdk_keyval_to_unicode(key);
1672 		if ((u < 0x80) && (u != key)) u = 0; // Alternative key
1673 #endif
1674 		if (u == ' ') name = "Space";
1675 		else if (u == '\\') name = "Backslash";
1676 #if GTK_MAJOR_VERSION == 1
1677 		else if (u < 0x100)
1678 		{
1679 			tbuf[0] = toupper(u);
1680 			tbuf[1] = 0;
1681 		}
1682 #else /* #if GTK_MAJOR_VERSION >= 2 */
1683 		else if (u && g_unichar_isgraph(u))
1684 			tbuf[g_unichar_to_utf8(g_unichar_toupper(u), tbuf)] = 0;
1685 #endif
1686 		else
1687 		{
1688 			char *s = gdk_keyval_name(gdk_keyval_to_lower(key));
1689 			int c, i = 0;
1690 
1691 			if (s) while (i < sizeof(tbuf) - 1)
1692 			{
1693 				if (!(c = s[i])) break;
1694 				if (c == '_') c = ' ';
1695 				tbuf[i++] = c;
1696 			}
1697 			tbuf[i] = 0;
1698 			if ((i == 1) && (tbuf[0] >= 'a') && (tbuf[0] <= 'z'))
1699 				tbuf[0] -= 'a' - 'A';
1700 		}
1701 	}
1702 	wjstrcat(buf, buflen, "", 0, m1, m2, m3, sp, name, NULL);
1703 }
1704 
parse_keyname(char * name,guint * mod)1705 static guint parse_keyname(char *name, guint *mod)
1706 {
1707 	guint m = 0;
1708 	char c;
1709 
1710 	while (TRUE) switch (c = *name++)
1711 	{
1712 	case 'C': m |= _Cmask; continue;
1713 	case 'S': m |= _Smask; continue;
1714 	case 'A': m |= _Amask; continue;
1715 	case '_':
1716 		*mod = m;
1717 		return (gdk_keyval_from_name(name));
1718 	default: return (KEY(VoidSymbol));
1719 	}
1720 }
1721 
1722 #define SEC_KEYNAMES ">keys<" /* Impossible name */
1723 
key_name(guint key,guint mod,int section)1724 static char *key_name(guint key, guint mod, int section)
1725 {
1726 	char buf[256];
1727 	int nk, ns;
1728 
1729 	/* Refer from unique ID to keyval */
1730 	get_keyname(buf, sizeof(buf), key, mod, FALSE);
1731 // !!! Maybe pack state into upper bits, like Qt does?
1732 	nk = ini_setint(&main_ini, section, buf, key);
1733 	/* Refer from display name to ID slot (the names cannot clash) */
1734 	get_keyname(buf, sizeof(buf), key, mod, TRUE);
1735 	ns = ini_setref(&main_ini, section, buf, nk);
1736 
1737 	return (INI_KEY(&main_ini, ns));
1738 }
1739 
1740 //	Keymap handling
1741 
1742 typedef struct {
1743 	guint key, mod, keycode;
1744 	int idx;
1745 } keyinfo;
1746 
1747 typedef struct {
1748 	void **slot;
1749 	int section;
1750 	int idx;	// Used for various mapping indices
1751 	guint key, mod;	// Last one labeled on menuitem
1752 } keyslot;
1753 
1754 typedef struct {
1755 	GtkAccelGroup *ag;
1756 	void ***res;			// Pointer to response var
1757 	int nkeys, nslots;		// Counters
1758 	int slotsec, keysec;		// Sections
1759 	int updated;			// Already matches INI
1760 	keyinfo map[MAX_KEYS];		// Keys
1761 	keyslot slots[1];		// Widgets (slot #0 stays empty)
1762 } keymap_data;
1763 
1764 #define KEYMAP_SIZE(N) (sizeof(keymap_data) + (N) * sizeof(keyslot))
1765 
1766 // !!! And with inlining this, problem also
add_sec_name(memx2 * mem,int base,int sec)1767 int add_sec_name(memx2 *mem, int base, int sec)
1768 {
1769 	char *s, *stack[CONT_DEPTH];
1770 	int n = 0, h = mem->here;
1771 
1772 	while (sec != base)
1773 	{
1774 		s = _(INI_KEY(&main_ini, sec));
1775 		stack[n++] = s + strspn(s, "/");
1776 		sec = INI_PARENT(&main_ini, sec);
1777 	}
1778 	while (n-- > 0)
1779 	{
1780 		addstr(mem, stack[n], 1);
1781 		addchars(mem, '/', 1);
1782 	}
1783 	mem->buf[mem->here - 1] = '\0';
1784 	return (h);
1785 }
1786 
keymap_export(keymap_data * keymap)1787 static keymap_dd *keymap_export(keymap_data *keymap)
1788 {
1789 	memx2 mem;
1790 	keymap_dd *km;
1791 	keyinfo *kf;
1792 	int lp = sizeof(keymap_dd) + sizeof(char *) * (keymap->nslots + 1);
1793 	int i, j, k, sec;
1794 
1795 	memset(&mem, 0, sizeof(mem));
1796 	getmemx2(&mem, lp + sizeof(key_dd) * MAX_KEYS);
1797 
1798 	/* First, stuff the names into memory block */
1799 	mem.here = lp;
1800 	for (i = j = 1; i <= keymap->nslots; i++)
1801 	{
1802 		sec = keymap->slots[i].section;
1803 		k = -1;
1804 		if (sec > 0)
1805 		{
1806 			k = add_sec_name(&mem, keymap->slotsec, sec);
1807 			((char **)(mem.buf + sizeof(keymap_dd)))[j] = (char *)k;
1808 			k = j++;
1809 		}
1810 		keymap->slots[i].idx = k;
1811 	}
1812 
1813 	lp = mem.here;
1814 	getmemx2(&mem, sizeof(key_dd) * (MAX_KEYS + 1));
1815 
1816 	/* Now the block is at its final size, so set the pointers */
1817 	km = (void *)mem.buf;
1818 	km->nslots = j - 1;
1819 	km->nkeys = keymap->nkeys;
1820 	km->maxkeys = MAX_KEYS;
1821 	km->keys = ALIGNED(mem.buf + lp, ALIGNOF(key_dd));
1822 	km->slotnames = (char **)(mem.buf + sizeof(keymap_dd));
1823 	km->slotnames[0] = NULL;
1824 	for (i = 1; i < j; i++)
1825 		km->slotnames[i] = mem.buf + (int)km->slotnames[i];
1826 
1827 	/* Now fill the keys table */
1828 	sec = ini_setsection(&main_ini, 0, SEC_KEYNAMES);
1829 	ini_transient(&main_ini, sec, NULL);
1830 	for (kf = keymap->map , i = 0; i < keymap->nkeys; i++ , kf++)
1831 	{
1832 		km->keys[i].slot = keymap->slots[kf->idx].idx;
1833 		km->keys[i].name = key_name(kf->key, kf->mod, sec);
1834 	}
1835 
1836 	return (km);
1837 }
1838 
keymap_add(keymap_data * keymap,void ** slot,char * name,int section)1839 static int keymap_add(keymap_data *keymap, void **slot, char *name, int section)
1840 {
1841 	keyslot *ks = keymap->slots + ++keymap->nslots;
1842 	ks->section = !name ? 0 :
1843 		ini_setint(&main_ini, section, name, keymap->nslots);
1844 	ks->slot = slot;
1845 	ks->idx = -1; // No mapping
1846 	return (TRUE);
1847 }
1848 
keymap_map(keymap_data * keymap,int idx,guint key,guint mod)1849 static int keymap_map(keymap_data *keymap, int idx, guint key, guint mod)
1850 {
1851 	keyinfo *kf = keymap->map + keymap->nkeys;
1852 
1853 	if (!key || (keymap->nkeys >= MAX_KEYS)) return (FALSE);
1854 	kf->key = key;
1855 	kf->mod = mod;
1856 	kf->idx = idx;
1857 	keymap->nkeys++;
1858 	return (TRUE);
1859 }
1860 
keymap_to_ini(keymap_data * keymap)1861 static void keymap_to_ini(keymap_data *keymap)
1862 {
1863 	char buf[256];
1864 	keyinfo *ki, *kd;
1865 	int i, sec, w = keymap->keysec, f = keymap->updated;
1866 
1867 	kd = ki = keymap->map;
1868 	for (i = keymap->nkeys; i-- > 0; ki++)
1869 	{
1870 		sec = keymap->slots[ki->idx].section;
1871 		if (f && sec) continue;
1872 		get_keyname(buf, sizeof(buf), ki->key, ki->mod, FALSE);
1873 		/* Fixed key - clear the ref, preserve the key */
1874 		if (!sec)
1875 		{
1876 			ini_setref(&main_ini, w, buf, 0);
1877 			*kd++ = *ki;
1878 		}
1879 		ini_getref(&main_ini, w, buf, sec);
1880 	}
1881 	keymap->nkeys = kd - keymap->map;
1882 	keymap->updated = TRUE;
1883 }
1884 
keymap_update(keymap_data * keymap,keymap_dd * keys)1885 static void keymap_update(keymap_data *keymap, keymap_dd *keys)
1886 {
1887 	char *nm;
1888 	guint key, mod;
1889 	int i, j, n, sec, w = keymap->keysec;
1890 
1891 	/* Make default keys into INI defaults */
1892 	if (!keymap->updated) keymap_to_ini(keymap);
1893 
1894 	/* Import keys into INI */
1895 	if (keys)
1896 	{
1897 		/* Clear everything existing */
1898 		for (i = INI_FIRST(&main_ini, w); i; i = INI_NEXT(&main_ini, i))
1899 			ini_setref(&main_ini, w, INI_KEY(&main_ini, i), 0);
1900 
1901 		/* Prepare reverse map */
1902 		for (i = j = 1; i <= keymap->nslots; i++)
1903 			if (keymap->slots[i].section > 0) keymap->slots[j++].idx = i;
1904 
1905 		/* Stuff things into inifile */
1906 		sec = ini_setsection(&main_ini, 0, SEC_KEYNAMES);
1907 		for (i = 0; i < keys->nkeys; i++)
1908 		{
1909 			j = keys->keys[i].slot;
1910 			if (j < 0) continue; // Fixed key
1911 			n = ini_getref(&main_ini, sec, keys->keys[i].name, 0);
1912 			if (n <= 0) continue; // Invalid key
1913 			ini_setref(&main_ini, w, INI_KEY(&main_ini, n),
1914 				keymap->slots[keymap->slots[j].idx].section);
1915 		}
1916 
1917 		/* Overwrite fixed keys again */
1918 		keymap_to_ini(keymap);
1919 	}
1920 
1921 	/* Step through the section reading it in */
1922 	for (i = INI_FIRST(&main_ini, w); i; i = INI_NEXT(&main_ini, i))
1923 	{
1924 		nm = INI_KEY(&main_ini, i);
1925 		key = parse_keyname(nm, &mod);
1926 		if (key == KEY(VoidSymbol)) continue;
1927 		sec = ini_getref(&main_ini, w, nm, 0);
1928 		if (sec <= 0) continue;
1929 		sec = (int)INI_VALUE(&main_ini, sec);
1930 		if ((sec > 0) && (sec <= keymap->nslots))
1931 			keymap_map(keymap, sec, key, mod);
1932 	}
1933 }
1934 
1935 #define FAKE_ACCEL "<f>/a/k/e"
1936 
1937 // !!! And with inlining this, problem also
keymap_init(keymap_data * keymap,keymap_dd * keys)1938 void keymap_init(keymap_data *keymap, keymap_dd *keys)
1939 {
1940 	char buf[256], *nm;
1941 	GtkWidget *w;
1942 	keyslot *ks;
1943 	guint key, mod, key0, mod0;
1944 	int i, j, op;
1945 
1946 	/* Ensure keys section */
1947 	nm = INI_KEY(&main_ini, keymap->slotsec);
1948 	j = strlen(nm);
1949 	wjstrcat(buf, sizeof(buf), nm, j, " keys" + !j, NULL);
1950 	j = ini_getsection(&main_ini, 0, buf);
1951 	keymap->keysec = ini_setsection(&main_ini, 0, buf);
1952 
1953 	/* If nondefault keys are there */
1954 	if (keys || ((j > 0) && !keymap->updated))
1955 		keymap_update(keymap, keys);
1956 
1957 	/* Label new key indices on slots - back to forth */
1958 	i = keymap->nslots + 1;
1959 	while (--i > 0) keymap->slots[i].idx = -1;
1960 	i = keymap->nkeys;
1961 	while (i-- > 0) keymap->slots[keymap->map[i].idx].idx = i;
1962 
1963 	/* Update keys on menuitems */
1964 	for (ks = keymap->slots + 1 , i = keymap->nslots; i > 0; i-- , ks++)
1965 	{
1966 		key = mod = 0;
1967 		j = ks->idx;
1968 		if (j >= 0) key = keymap->map[j].key , mod = keymap->map[j].mod;
1969 		key0 = ks->key; mod0 = ks->mod;
1970 		ks->key = key; ks->mod = mod; // Update
1971 		if (!((key ^ key0) | (mod ^ mod0))) continue; // Unchanged
1972 		// No matter, if not a menuitem
1973 		op = GET_OP(ks->slot);
1974 		if ((op < op_MENU_0) || (op >= op_MENU_LAST)) continue;
1975 		// Tell widget about update
1976 		w = ks->slot[0];
1977 		if (key0 | mod0) gtk_widget_remove_accelerator(w, keymap->ag,
1978 			key0, mod0);
1979 #if GTK_MAJOR_VERSION >= 2
1980 		/* !!! It has to be there in place of key, for menu spacing */
1981 		gtk_widget_set_accel_path(w, j < 0 ? FAKE_ACCEL : NULL, keymap->ag);
1982 #endif
1983 		if (j >= 0) gtk_widget_add_accelerator(w, "activate", keymap->ag,
1984 			key, mod, GTK_ACCEL_VISIBLE);
1985 	}
1986 }
1987 
keymap_reset(keymap_data * keymap)1988 static void keymap_reset(keymap_data *keymap)
1989 {
1990 	keyinfo *kf = keymap->map;
1991 	int i = keymap->nkeys;
1992 
1993 	while (i-- > 0) kf->keycode = keyval_key(kf->key) , kf++;
1994 }
1995 
keymap_find(keymap_data * keymap,key_ext * key)1996 static void keymap_find(keymap_data *keymap, key_ext *key)
1997 {
1998 	keyinfo *kf, *match = NULL;
1999 	int i;
2000 
2001 	for (kf = keymap->map , i = keymap->nkeys; i > 0; i-- , kf++)
2002 	{
2003 		/* Modifiers should match first */
2004 		if ((key->state & _CSAmask) != kf->mod) continue;
2005 		/* Let keyval have priority; this is also a workaround for
2006 		 * GTK2 bug #136280 */
2007 		if (key->lowkey == kf->key) break;
2008 		/* Let keycodes match when keyvals don't */
2009 		if (key->realkey == kf->keycode) match = kf;
2010 	}
2011 	/* Keyval match */
2012 	if (i > 0) match = kf;
2013 /* !!! If the starting layout has the keyval+mods combo mapped to one key, and
2014  * the current layout to another, both will work till "rebind keys" is done.
2015  * I like this better than shortcuts moving with every layout switch - WJ */
2016 	/* If we have at least a keycode match */
2017 	*(keymap->res) = match ? keymap->slots[match->idx].slot : NULL;
2018 }
2019 
2020 //	KEYBUTTON widget
2021 
2022 typedef struct {
2023 	int section;
2024 	guint key, mod;
2025 } keybutton_data;
2026 
convert_key(GtkWidget * widget,GdkEventKey * event,gpointer user_data)2027 static gboolean convert_key(GtkWidget *widget, GdkEventKey *event,
2028 	gpointer user_data)
2029 {
2030 	keybutton_data *dt = user_data;
2031 	char buf[256];
2032 
2033 	/* Do nothing until button is pressed */
2034 	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
2035 		return (FALSE);
2036 
2037 	/* Store keypress, display its name */
2038 	if (event->type == GDK_KEY_PRESS)
2039 	{
2040 		get_keyname(buf, sizeof(buf), dt->key = low_key(event),
2041 			dt->mod = event->state, TRUE);
2042 		gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(widget))), buf);
2043 	}
2044 	/* Depress button when key gets released */
2045 	else /* if (event->type == GDK_KEY_RELEASE) */
2046 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
2047 
2048 	return (TRUE);
2049 }
2050 
add_del(void ** r,GtkWidget * window)2051 static void add_del(void **r, GtkWidget *window)
2052 {
2053 	gtk_signal_connect(GTK_OBJECT(window), "delete_event",
2054 		GTK_SIGNAL_FUNC(get_evt_del), r);
2055 }
2056 
skip_if(void ** pp)2057 static void **skip_if(void **pp)
2058 {
2059 	int lp, mk;
2060 	void **ifcode;
2061 
2062 	ifcode = pp + 1 + (lp = WB_GETLEN((int)*pp));
2063 	if (lp > 1) // skip till corresponding ENDIF
2064 	{
2065 		mk = (int)pp[2];
2066 		while ((((int)*ifcode & WB_OPMASK) != op_ENDIF) ||
2067 			((int)ifcode[1] != mk))
2068 			ifcode += 1 + WB_GETLEN((int)*ifcode);
2069 	}
2070 	return (ifcode + 1 + (WB_GETLEN((int)*ifcode)));
2071 }
2072 
2073 /* Trigger events which need triggering */
trigger_things(void ** wdata)2074 static void trigger_things(void **wdata)
2075 {
2076 	char *data = GET_DDATA(wdata);
2077 	void **slot, **base, **desc;
2078 
2079 	for (wdata = GET_WINDOW(wdata); wdata[1]; wdata = NEXT_SLOT(wdata))
2080 	{
2081 		int opf = GET_OPF(wdata), op = opf & WB_OPMASK;
2082 		if (IS_UNREAL(wdata)) op = GET_UOP(wdata);
2083 
2084 		/* Trigger events for nested widget */
2085 		if (op == op_MOUNT)
2086 		{
2087 			if ((slot = wdata[2])) trigger_things(slot);
2088 			continue;
2089 		}
2090 		if (op == op_uMOUNT)
2091 		{
2092 			if ((slot = ((swdata *)*wdata)->strs))
2093 				trigger_things(slot);
2094 			continue;
2095 		}
2096 		/* Prepare preset split widget */
2097 		if ((op == op_HVSPLIT) && WB_GETLEN(opf))
2098 		{
2099 			cmd_set(wdata, (int)GET_DESCV(wdata, 1));
2100 			continue;
2101 		}
2102 
2103 		if (op != op_TRIGGER) continue;
2104 		if (!WB_GETLEN(opf)) // Regular version
2105 		{
2106 			slot = PREV_SLOT(wdata);
2107 			while (GET_OP(slot) > op_EVT_LAST) // Find EVT slot
2108 				slot = PREV_SLOT(slot);
2109 			base = slot;
2110 		}
2111 		else // Version for menu/toolbar items
2112 		{
2113 			/* Here, event is put into next slot, and widget is
2114 			 * in nearest widgetlike slot before */
2115 			base = NEXT_SLOT(wdata);
2116 			slot = origin_slot(wdata);
2117 		}
2118 		desc = base[1];
2119 		((evt_fn)desc[1])(data, base[0], (int)desc[0] & WB_OPMASK, slot);
2120 	}
2121 }
2122 
2123 #if GTK_MAJOR_VERSION == 3
2124 
2125 /* Wait till window is getting drawn to make it user-resizable */
make_resizable(GtkWidget * widget,cairo_t * cr,gpointer user_data)2126 static gboolean make_resizable(GtkWidget *widget, cairo_t *cr, gpointer user_data)
2127 {
2128 	gtk_window_set_resizable(GTK_WINDOW(widget), TRUE);
2129 	g_signal_handlers_disconnect_by_func(widget, make_resizable, user_data);
2130 	return (FALSE);
2131 }
2132 
2133 /* Reintroduce sanity to sizing wrapped labels */
do_rewrap(GtkWidget * widget,gint vert,gint * min,gint * nat,gint for_width,gpointer user_data)2134 static void do_rewrap(GtkWidget *widget, gint vert, gint *min, gint *nat,
2135 	gint for_width, gpointer user_data)
2136 {
2137 	PangoLayout *layout;
2138 	PangoRectangle r;
2139 	GtkBorder pb;
2140 	gint xpad;
2141 	int wmax, w, h, ww, sw2, lines, w2l;
2142 
2143 //g_print("vert %d for_width %d min %d nat %d\n", vert, for_width, *min, *nat);
2144 	if (vert) return; // Do nothing for vertical size
2145 
2146 	/* Max wrap width */
2147 	layout = gtk_widget_create_pango_layout(widget,
2148 		/* Let wrap width be 65 chars */
2149 		"88888888888888888888888888888888"
2150 		"888888888888888888888888888888888");
2151 	pango_layout_get_size(layout, &ww, NULL);
2152 	sw2 = PANGO_SCALE * ((gdk_screen_get_width(gtk_widget_get_screen(widget)) + 1) / 2);
2153 	if (ww > sw2) ww = sw2;
2154 
2155 	/* Full text width */
2156 	pango_layout_set_text(layout, gtk_label_get_text(GTK_LABEL(widget)), -1);
2157 	pango_layout_set_alignment(layout, gtk_widget_get_direction(widget) ==
2158 		GTK_TEXT_DIR_RTL ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT);
2159 	pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
2160 	pango_layout_set_width(layout, -1);
2161 	pango_layout_get_extents(layout, NULL, &r);
2162 	wmax = r.width;
2163 
2164 	if (wmax > ww) /* Wrap need happen */
2165 	{
2166 		pango_layout_set_width(layout, ww);
2167 		pango_layout_get_extents(layout, NULL, &r);
2168 		w = r.width;
2169 		h = r.height;
2170 
2171 		/* Maybe make narrower, to rebalance lines */
2172 		lines = pango_layout_get_line_count(layout);
2173 		w2l = (wmax + lines - 1) / lines;
2174 		if (w2l < w)
2175 		{
2176 			pango_layout_set_width(layout, w2l);
2177 			pango_layout_get_extents(layout, NULL, &r);
2178 			if (r.height > h) // Extra line(s) got added - make wider
2179 			{
2180 				pango_layout_set_width(layout, (w2l + w) / 2);
2181 				pango_layout_get_extents(layout, NULL, &r);
2182 			}
2183 			if (r.height <= h) w = r.width; // No extra lines happened
2184 		}
2185 
2186 		/* Full width in pixels */
2187 		w = w / PANGO_SCALE + 1;
2188 		get_padding_and_border(gtk_widget_get_style_context(widget),
2189 			NULL, NULL, &pb);
2190 		gtk_misc_get_padding(GTK_MISC(widget), &xpad, NULL);
2191 		w += pb.left + pb.right + 2 * xpad;
2192 	}
2193 	else w = *nat; /* Take what label wants */
2194 
2195 //g_print("Want nat %d\n", w);
2196 	if (w > *nat) w = *nat; // If widget wants even less
2197 	if (w < *min) w = *min; // Respect the minsize
2198 
2199 	/* Reduce natural width to this */
2200 	/* !!! Make minimum and natural width the same: otherwise, bug in GTK+3
2201 	 * size negotiation causes labels be too tall; the longer the text, the
2202 	 * taller the label - WJ */
2203 	*min = *nat = w;
2204 //g_print("Now nat %d\n", *nat);
2205 
2206 	g_object_unref(layout);
2207 }
2208 
2209 /* Apply minimum width from the given widget to this one */
do_shorten(GtkWidget * widget,gint vert,gint * min,gint * nat,gint for_width,gpointer user_data)2210 static void do_shorten(GtkWidget *widget, gint vert, gint *min, gint *nat,
2211 	gint for_width, gpointer user_data)
2212 {
2213 	if (vert) return; // Do nothing for vertical size
2214 	gtk_widget_get_preferred_width(user_data, min, NULL);
2215 }
2216 
2217 #if GTK3VERSION < 22 /* No gtk_scrolled_window_set_propagate_natural_*() */
2218 /* Try to avoid scrolling - request natural size of contents */
do_wantmax(GtkWidget * widget,gint vert,gint * min,gint * nat,gint for_width,gpointer user_data)2219 static void do_wantmax(GtkWidget *widget, gint vert, gint *min, gint *nat,
2220 	gint for_width, gpointer user_data)
2221 {
2222 	GtkWidget *inside = gtk_bin_get_child(GTK_BIN(widget));
2223 	gint cmin = 0, cnat = 0;
2224 
2225 	if (!inside) return; // Nothing to do
2226 	if (((int)user_data & 1) && !vert) return; // Leave width be
2227 	if (((int)user_data & 2) && vert) return; // Leave height be
2228 	if (for_width >= 0)
2229 		gtk_widget_get_preferred_height_for_width(inside, for_width, &cmin, &cnat);
2230 	else (vert ? gtk_widget_get_preferred_height :
2231 		gtk_widget_get_preferred_width)(inside, &cmin, &cnat);
2232 	if (cnat > *nat) *nat = cnat;
2233 }
2234 #endif
2235 
2236 #else /* #if GTK_MAJOR_VERSION <= 2 */
2237 
2238 /* Try to avoid scrolling - request full size of contents */
scroll_max_size_req(GtkWidget * widget,GtkRequisition * requisition,gpointer user_data)2239 static void scroll_max_size_req(GtkWidget *widget, GtkRequisition *requisition,
2240 	gpointer user_data)
2241 {
2242 	GtkWidget *child = GTK_BIN(widget)->child;
2243 
2244 	if (child && GTK_WIDGET_VISIBLE(child))
2245 	{
2246 		GtkRequisition wreq;
2247 		int n, border = GTK_CONTAINER(widget)->border_width * 2;
2248 
2249 		gtk_widget_get_child_requisition(child, &wreq);
2250 		n = wreq.width + border;
2251 		if ((requisition->width < n) && !((int)user_data & 1))
2252 			requisition->width = n;
2253 		n = wreq.height + border;
2254 		if ((requisition->height < n) && !((int)user_data & 2))
2255 			requisition->height = n;
2256 	}
2257 }
2258 
2259 #endif
2260 
2261 // !!! And with inlining this, problem also
scrollw(int vh)2262 GtkWidget *scrollw(int vh)
2263 {
2264 	static const int scrollp[3] = { GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC,
2265 		GTK_POLICY_ALWAYS };
2266 	GtkWidget *widget = gtk_scrolled_window_new(NULL, NULL);
2267 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget),
2268 		scrollp[vh & 255], scrollp[vh >> 8]);
2269 #if GTK3VERSION >= 22
2270 	/* To fix newly-broken sizing */
2271 	if (!(vh & 255)) gtk_scrolled_window_set_propagate_natural_width(
2272 			GTK_SCROLLED_WINDOW(widget), TRUE);
2273 	if (!(vh >> 8)) gtk_scrolled_window_set_propagate_natural_height(
2274 			GTK_SCROLLED_WINDOW(widget), TRUE);
2275 #endif
2276 	return (widget);
2277 }
2278 
2279 /* Toggle notebook pages */
toggle_vbook(GtkToggleButton * button,gpointer user_data)2280 static void toggle_vbook(GtkToggleButton *button, gpointer user_data)
2281 {
2282 	gtk_notebook_set_page(**(void ***)user_data,
2283 		!!gtk_toggle_button_get_active(button));
2284 }
2285 
2286 #if GTK_MAJOR_VERSION == 3
2287 
2288 //	RGBIMAGE widget
2289 
2290 typedef struct {
2291 	cairo_surface_t *s;
2292 	unsigned char *rgb;
2293 	int w, h, bkg;
2294 } rgbimage_data;
2295 
redraw_rgb(GtkWidget * widget,cairo_t * cr,gpointer user_data)2296 static gboolean redraw_rgb(GtkWidget *widget, cairo_t *cr, gpointer user_data)
2297 {
2298 	rgbimage_data *rd = user_data;
2299 	GdkRectangle r;
2300 	int x2, y2, rxy[4] = { 0, 0, rd->w, rd->h };
2301 
2302 
2303 	if (!gdk_cairo_get_clip_rectangle(cr, &r)) return (TRUE); // Nothing to do
2304 
2305 	cairo_save(cr);
2306 	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
2307 
2308 	if (!clip(rxy, r.x, r.y, x2 = r.x + r.width, y2 = r.y + r.height, rxy) ||
2309 		!rd->rgb) rxy[2] = r.x , rxy[3] = y2;
2310 	else
2311 	{
2312 		/* RGB image buffer */
2313 		if (!rd->s) rd->s = cairo_upload_rgb(NULL, gtk_widget_get_window(widget),
2314 			rd->rgb, rd->w, rd->h, rd->w * 3);
2315 		cairo_set_source_surface(cr, rd->s, 0, 0);
2316 		cairo_unfilter(cr);
2317 		cairo_rectangle(cr, 0, 0, rd->w, rd->h); // Let Cairo clip it
2318 		cairo_fill(cr);
2319 	}
2320 
2321 	/* Opaque background outside image proper */
2322 	cairo_set_rgb(cr, rd->bkg);
2323 	if (rxy[2] < x2)
2324 	{
2325 		cairo_rectangle(cr, rxy[2], r.y, x2 - rxy[2], rxy[3] - rxy[1]);
2326 		cairo_fill(cr);
2327 	}
2328 	if (rxy[3] < y2)
2329 	{
2330 		cairo_rectangle(cr, r.x, rxy[3], r.width, y2 - rxy[3]);
2331 		cairo_fill(cr);
2332 	}
2333 
2334 	cairo_restore(cr);
2335 
2336 	return (TRUE);
2337 }
2338 
reset_rgb(GtkWidget * widget,gpointer user_data)2339 static void reset_rgb(GtkWidget *widget, gpointer user_data)
2340 {
2341 	rgbimage_data *rd = user_data;
2342 	if (rd->s) cairo_surface_fdestroy(rd->s);
2343 	rd->s = NULL;
2344 }
2345 
rgbimage(void ** r,int * wh)2346 GtkWidget *rgbimage(void **r, int *wh)
2347 {
2348 	GtkWidget *widget;
2349 	rgbimage_data *rd = r[2];
2350 
2351 	widget = gtk_drawing_area_new();
2352 	rd->rgb = r[0];
2353 	rd->w = wh[0];
2354 	rd->h = wh[1];
2355 	gtk_widget_set_size_request(widget, wh[0], wh[1]);
2356 	g_signal_connect(G_OBJECT(widget), "unrealize", G_CALLBACK(reset_rgb), rd);
2357 	g_signal_connect(G_OBJECT(widget), "draw", G_CALLBACK(redraw_rgb), rd);
2358 
2359 	return (widget);
2360 }
2361 
2362 //	RGBIMAGEP widget
2363 
redraw_rgbp(GtkWidget * widget,cairo_t * cr,gpointer user_data)2364 static gboolean redraw_rgbp(GtkWidget *widget, cairo_t *cr, gpointer user_data)
2365 {
2366 	rgbimage_data *rd = user_data;
2367 	GtkAllocation alloc;
2368 	int x, y;
2369 
2370 	if (!rd->s) return (TRUE);
2371 	gtk_widget_get_allocation(widget, &alloc);
2372 	x = (alloc.width - rd->w) / 2;
2373 	y = (alloc.height - rd->h) / 2;
2374 
2375 	cairo_save(cr);
2376 	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
2377 	cairo_set_source_surface(cr, rd->s, x, y);
2378 	cairo_rectangle(cr, x, y, rd->w, rd->h);
2379 	cairo_fill(cr);
2380 	cairo_restore(cr);
2381 
2382 	return (TRUE);
2383 }
2384 
reset_rgbp(GtkWidget * widget,gpointer user_data)2385 static void reset_rgbp(GtkWidget *widget, gpointer user_data)
2386 {
2387 	rgbimage_data *rd = user_data;
2388 	cairo_surface_t *s;
2389 	cairo_t *cr;
2390 
2391 	if (!rd->s)
2392 	{
2393 		GdkWindow *win = gtk_widget_get_window(widget);
2394 		if (!win) win = gdk_screen_get_root_window(gtk_widget_get_screen(widget));
2395 		rd->s = gdk_window_create_similar_surface(win, CAIRO_CONTENT_COLOR,
2396 			rd->w, rd->h);
2397 	}
2398 
2399 	s = cairo_upload_rgb(rd->s, NULL, rd->rgb, rd->w, rd->h, rd->w * 3);
2400 	cr = cairo_create(rd->s);
2401 	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
2402 	cairo_set_source_surface(cr, s, 0, 0);
2403 	cairo_unfilter(cr);
2404 	cairo_paint(cr);
2405 	cairo_destroy(cr);
2406 	cairo_surface_fdestroy(s);
2407 
2408 	gtk_widget_queue_draw(widget);
2409 }
2410 
2411 // !!! And with inlining this, problem also
rgbimagep(void ** r,int w,int h)2412 GtkWidget *rgbimagep(void **r, int w, int h)
2413 {
2414 	rgbimage_data *rd = r[2];
2415 	GtkWidget *widget;
2416 
2417 	/* With GtkImage unable to properly hold surfaces till GTK+ 3.20, have
2418 	 * to use a window-less container as substitute */
2419 	widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2420 	gtk_widget_set_size_request(widget, w, h);
2421 
2422 	rd->rgb = r[0];
2423 	rd->w = w;
2424 	rd->h = h;
2425 	g_signal_connect(G_OBJECT(widget), "realize", G_CALLBACK(reset_rgbp), rd);
2426 	g_signal_connect(G_OBJECT(widget), "unrealize", G_CALLBACK(reset_rgb), rd);
2427 	g_signal_connect(G_OBJECT(widget), "draw", G_CALLBACK(redraw_rgbp), rd);
2428 
2429 	return (widget);
2430 }
2431 
2432 #else /* if GTK_MAJOR_VERSION <= 2 */
2433 
2434 //	RGBIMAGE widget
2435 
2436 typedef struct {
2437 	unsigned char *rgb;
2438 	int w, h, bkg;
2439 } rgbimage_data;
2440 
expose_ext(GtkWidget * widget,GdkEventExpose * event,rgbimage_data * rd,int dx,int dy)2441 static void expose_ext(GtkWidget *widget, GdkEventExpose *event,
2442 	rgbimage_data *rd, int dx, int dy)
2443 {
2444 	GdkGCValues sv;
2445 	GdkGC *gc = widget->style->black_gc;
2446 	unsigned char *src = rd->rgb;
2447 	int w = rd->w, h = rd->h, bkg = 0;
2448 	int x1, y1, x2, y2, rxy[4] = { 0, 0, w, h };
2449 
2450 	x2 = (x1 = event->area.x + dx) + event->area.width;
2451 	y2 = (y1 = event->area.y + dy) + event->area.height;
2452 
2453 	if (!clip(rxy, x1, y1, x2, y2, rxy) || !src) rxy[2] = x1 , rxy[3] = y2;
2454 	else gdk_draw_rgb_image(widget->window, gc,
2455 		event->area.x, event->area.y, rxy[2] - rxy[0], rxy[3] - rxy[1],
2456 		GDK_RGB_DITHER_NONE, src + (y1 * w + x1) * 3, w * 3);
2457 
2458 	/* With theme engines lurking out there, weirdest things can happen */
2459 	if (((rxy[2] < x2) || (rxy[3] < y2)) && (bkg = rd->bkg))
2460 	{
2461 		gdk_gc_get_values(gc, &sv);
2462 		gdk_rgb_gc_set_foreground(gc, bkg);
2463 	}
2464 	if (rxy[2] < x2) gdk_draw_rectangle(widget->window, gc,
2465 		TRUE, rxy[2] - dx, event->area.y,
2466 		x2 - rxy[2], rxy[3] - rxy[1]);
2467 	if (rxy[3] < y2) gdk_draw_rectangle(widget->window, gc,
2468 		TRUE, event->area.x, rxy[3] - dy,
2469 		event->area.width, y2 - rxy[3]);
2470 	if (bkg) gdk_gc_set_foreground(gc, &sv.foreground);
2471 }
2472 
expose_rgb(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)2473 static gboolean expose_rgb(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
2474 {
2475 	expose_ext(widget, event, user_data, 0, 0);
2476 	return (TRUE);
2477 }
2478 
2479 // !!! And with inlining this, problem also
rgbimage(void ** r,int * wh)2480 GtkWidget *rgbimage(void **r, int *wh)
2481 {
2482 	GtkWidget *widget;
2483 	rgbimage_data *rd = r[2];
2484 
2485 	widget = gtk_drawing_area_new();
2486 	rd->rgb = r[0];
2487 	rd->w = wh[0];
2488 	rd->h = wh[1];
2489 	gtk_widget_set_usize(widget, wh[0], wh[1]);
2490 	gtk_signal_connect(GTK_OBJECT(widget), "expose_event",
2491 		GTK_SIGNAL_FUNC(expose_rgb), rd);
2492 
2493 	return (widget);
2494 }
2495 
2496 //	RGBIMAGEP widget
2497 
reset_rgbp(GtkWidget * widget,gpointer user_data)2498 static void reset_rgbp(GtkWidget *widget, gpointer user_data)
2499 {
2500 	rgbimage_data *rd = user_data;
2501 	GdkPixmap *pmap;
2502 
2503 	gtk_pixmap_get(GTK_PIXMAP(widget), &pmap, NULL);
2504 	gdk_draw_rgb_image(pmap, widget->style->black_gc,
2505 		0, 0, rd->w, rd->h, GDK_RGB_DITHER_NONE,
2506 		rd->rgb, rd->w * 3);
2507 	gtk_widget_queue_draw(widget);
2508 }
2509 
2510 // !!! And with inlining this, problem also
rgbimagep(void ** r,int w,int h)2511 GtkWidget *rgbimagep(void **r, int w, int h)
2512 {
2513 	rgbimage_data *rd = r[2];
2514 	GdkPixmap *pmap;
2515 	GtkWidget *widget;
2516 
2517 	pmap = gdk_pixmap_new(main_window->window, w, h, -1);
2518 	widget = gtk_pixmap_new(pmap, NULL);
2519 	gdk_pixmap_unref(pmap);
2520 	gtk_pixmap_set_build_insensitive(GTK_PIXMAP(widget), FALSE);
2521 
2522 	rd->rgb = r[0];
2523 	rd->w = w;
2524 	rd->h = h;
2525 	gtk_signal_connect(GTK_OBJECT(widget), "realize",
2526 		GTK_SIGNAL_FUNC(reset_rgbp), rd);
2527 
2528 	return (widget);
2529 }
2530 
2531 #endif /* GTK+1&2 */
2532 
2533 //	CANVASIMG widget
2534 
2535 #if GTK_MAJOR_VERSION == 3
2536 
expose_canvasimg(GtkWidget * widget,cairo_region_t * clip_r,gpointer user_data)2537 static void expose_canvasimg(GtkWidget *widget, cairo_region_t *clip_r,
2538 	gpointer user_data)
2539 {
2540 	rgbimage_data *rd = user_data;
2541 	cairo_rectangle_int_t r;
2542 	int x2, y2, rxy[4] = { 0, 0, rd->w, rd->h };
2543 
2544 
2545 	cairo_region_get_extents(clip_r, &r);
2546 	if (!clip(rxy, r.x, r.y, x2 = r.x + r.width, y2 = r.y + r.height, rxy) ||
2547 		!rd->rgb) rxy[2] = r.x , rxy[3] = y2;
2548 	/* RGB image buffer */
2549 	else wjcanvas_draw_rgb(widget, rxy[0], rxy[1], rxy[2] - rxy[0], rxy[3] - rxy[1],
2550 		rd->rgb + (rxy[1] * rd->w + rxy[0]) * 3, rd->w * 3, 0, FALSE);
2551 
2552 	/* Opaque background outside image proper */
2553 	if (rxy[2] < x2) wjcanvas_draw_rgb(widget, rxy[2], r.y,
2554 		x2 - rxy[2], rxy[3] - rxy[1], NULL, 0, rd->bkg, FALSE);
2555 	if (rxy[3] < y2) wjcanvas_draw_rgb(widget, r.x, rxy[3],
2556 		r.width, y2 - rxy[3], NULL, 0, rd->bkg, FALSE);
2557 }
2558 
2559 #else /* if GTK_MAJOR_VERSION <= 2 */
2560 
expose_canvasimg(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)2561 static gboolean expose_canvasimg(GtkWidget *widget, GdkEventExpose *event,
2562 	gpointer user_data)
2563 {
2564 	int vport[4];
2565 
2566 	wjcanvas_get_vport(widget, vport);
2567 	expose_ext(widget, event, user_data, vport[0], vport[1]);
2568 	return (TRUE);
2569 }
2570 
2571 #endif /* GTK+1&2 */
2572 
canvasimg(void ** r,int w,int h,int bkg)2573 GtkWidget *canvasimg(void **r, int w, int h, int bkg)
2574 {
2575 	rgbimage_data *rd = r[2];
2576 	GtkWidget *widget, *frame;
2577 
2578 	widget = wjcanvas_new();
2579 	rd->rgb = r[0];
2580 	rd->w = w;
2581 	rd->h = h;
2582 	rd->bkg = bkg;
2583 	wjcanvas_size(widget, w, h);
2584 	wjcanvas_set_expose(widget, GTK_SIGNAL_FUNC(expose_canvasimg), rd);
2585 
2586 	frame = wjframe_new();
2587 	gtk_widget_show(frame);
2588 	gtk_container_add(GTK_CONTAINER(frame), widget);
2589 
2590 	return (widget);
2591 }
2592 
2593 //	CANVAS widget
2594 
2595 #if GTK_MAJOR_VERSION == 3
2596 
expose_canvas_(GtkWidget * widget,cairo_region_t * clip_r,gpointer user_data)2597 static void expose_canvas_(GtkWidget *widget, cairo_region_t *clip_r,
2598 	gpointer user_data)
2599 {
2600 	void **slot = user_data;
2601 	void **base = slot[0], **desc = slot[1];
2602 	cairo_rectangle_int_t re, rex;
2603 	rgbcontext ctx;
2604 	int m, s, sz, cost = (int)GET_DESCV(PREV_SLOT(slot), 2);
2605 	int i, n, wh, r1 = 0;
2606 
2607 	/* Analyze what we got */
2608 	cairo_region_get_extents(clip_r, &rex);
2609 	wh = rex.width * rex.height;
2610 	n = cairo_region_num_rectangles(clip_r);
2611 	for (i = m = sz = 0; i < n; i++)
2612 	{
2613 		cairo_region_get_rectangle(clip_r, i, &re);
2614 		sz += (s = re.width * re.height);
2615 		if (m < s) m = s;
2616 	}
2617 	/* Only bother with regions if worth it */
2618 	if (wh - sz <= cost * (n - 1)) r1 = n = 1 , m = wh , re = rex;
2619 
2620 	ctx.rgb = malloc(m * 3);
2621 	for (i = 0; i < n; i++)
2622 	{
2623 		if (!r1) cairo_region_get_rectangle(clip_r, i, &re);
2624 		ctx.xy[2] = (ctx.xy[0] = re.x) + re.width;
2625 		ctx.xy[3] = (ctx.xy[1] = re.y) + re.height;
2626 
2627 		if (((evtxr_fn)desc[1])(GET_DDATA(base), base,
2628 			(int)desc[0] & WB_OPMASK, slot, &ctx))
2629 // !!! Allow drawing area to be reduced, or ignored altogether
2630 			wjcanvas_draw_rgb(widget, ctx.xy[0], ctx.xy[1],
2631 				ctx.xy[2] - ctx.xy[0], ctx.xy[3] - ctx.xy[1],
2632 				ctx.rgb, (ctx.xy[2] - ctx.xy[0]) * 3, 0, FALSE);
2633 	}
2634 	free(ctx.rgb);
2635 }
2636 
2637 #else /* if GTK_MAJOR_VERSION <= 2 */
2638 
expose_canvas_(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)2639 static gboolean expose_canvas_(GtkWidget *widget, GdkEventExpose *event,
2640 	gpointer user_data)
2641 {
2642 	void **slot = user_data;
2643 	void **base = slot[0], **desc = slot[1];
2644 	GdkRectangle *r = &event->area;
2645 	rgbcontext ctx;
2646 	int vport[4];
2647 	int i, cnt = 1, wh = r->width * r->height;
2648 #if GTK_MAJOR_VERSION == 2 /* Maybe use regions */
2649 	GdkRectangle *rects;
2650 	gint nrects;
2651 	int m, s, sz, cost = (int)GET_DESCV(PREV_SLOT(slot), 2);
2652 
2653 	gdk_region_get_rectangles(event->region, &rects, &nrects);
2654 	for (i = m = sz = 0; i < nrects; i++)
2655 	{
2656 		sz += (s = rects[i].width * rects[i].height);
2657 		if (m < s) m = s;
2658 	}
2659 
2660 	/* Only bother with regions if worth it */
2661 	if (wh - sz > cost * (nrects - 1))
2662 		r = rects , cnt = nrects , wh = m;
2663 #endif
2664 	wjcanvas_get_vport(widget, vport);
2665 
2666 	ctx.rgb = malloc(wh * 3);
2667 	for (i = 0; i < cnt; i++)
2668 	{
2669 		ctx.xy[2] = (ctx.xy[0] = vport[0] + r[i].x) + r[i].width;
2670 		ctx.xy[3] = (ctx.xy[1] = vport[1] + r[i].y) + r[i].height;
2671 
2672 		if (((evtxr_fn)desc[1])(GET_DDATA(base), base,
2673 			(int)desc[0] & WB_OPMASK, slot, &ctx))
2674 // !!! Allow drawing area to be reduced, or ignored altogether
2675 			gdk_draw_rgb_image(widget->window, widget->style->black_gc,
2676 				ctx.xy[0] - vport[0], ctx.xy[1] - vport[1],
2677 				ctx.xy[2] - ctx.xy[0], ctx.xy[3] - ctx.xy[1],
2678 				GDK_RGB_DITHER_NONE, ctx.rgb,
2679 				(ctx.xy[2] - ctx.xy[0]) * 3);
2680 	}
2681 	free(ctx.rgb);
2682 
2683 #if GTK_MAJOR_VERSION == 2
2684 	g_free(rects);
2685 #endif
2686 	return (FALSE);
2687 }
2688 
2689 #endif /* GTK+1&2 */
2690 
2691 //	FCIMAGEP widget
2692 
2693 typedef struct {
2694 	void **mslot;
2695 	unsigned char *rgb;
2696 	int *xy;
2697 	int w, h, cursor;
2698 } fcimage_data;
2699 
reset_fcimage(GtkWidget * widget,gpointer user_data)2700 static void reset_fcimage(GtkWidget *widget, gpointer user_data)
2701 {
2702 	fcimage_data *fd = user_data;
2703 
2704 	if (!wjpixmap_pixmap(widget)) return;
2705 	wjpixmap_draw_rgb(widget, 0, 0, fd->w, fd->h, fd->rgb, fd->w * 3);
2706 	if (!fd->xy) return;
2707 	if (!fd->cursor) wjpixmap_set_cursor(widget,
2708 		xbm_ring4_bits, xbm_ring4_mask_bits,
2709 		xbm_ring4_width, xbm_ring4_height,
2710 		xbm_ring4_x_hot, xbm_ring4_y_hot, FALSE);
2711 	fd->cursor = TRUE;
2712 	wjpixmap_move_cursor(widget, fd->xy[0], fd->xy[1]);
2713 }
2714 
click_fcimage(GtkWidget * widget,GdkEventButton * event,gpointer user_data)2715 static gboolean click_fcimage(GtkWidget *widget, GdkEventButton *event,
2716 	gpointer user_data)
2717 {
2718 	gtk_widget_grab_focus(widget);
2719 	return (FALSE);
2720 }
2721 
2722 // !!! And with inlining this, problem also
fcimagep(void ** r,char * ddata)2723 GtkWidget *fcimagep(void **r, char *ddata)
2724 {
2725 	fcimage_data *fd = r[2];
2726 	void **pp = r[1];
2727 	GtkWidget *widget;
2728 	int w, h, *xy;
2729 
2730 	xy = (void *)(ddata + (int)pp[3]);
2731 	w = xy[0]; h = xy[1];
2732 	xy = pp[2] == (void *)(-1) ? NULL : (void *)(ddata + (int)pp[2]);
2733 
2734 	widget = wjpixmap_new(w, h);
2735 	fd->xy = xy;
2736 	fd->rgb = r[0];
2737 	fd->w = w;
2738 	fd->h = h;
2739 	gtk_signal_connect(GTK_OBJECT(widget), "realize",
2740 		GTK_SIGNAL_FUNC(reset_fcimage), fd);
2741 	gtk_signal_connect(GTK_OBJECT(widget), "button_press_event",
2742 		GTK_SIGNAL_FUNC(click_fcimage), NULL);
2743 	return (widget);
2744 }
2745 
fcimage_rxy(void ** slot,int * xy)2746 static void fcimage_rxy(void **slot, int *xy)
2747 {
2748 	fcimage_data *fd = slot[2];
2749 
2750 	wjpixmap_rxy(slot[0], xy[0], xy[1], xy + 0, xy + 1);
2751 	xy[0] = xy[0] < 0 ? 0 : xy[0] >= fd->w ? fd->w - 1 : xy[0];
2752 	xy[1] = xy[1] < 0 ? 0 : xy[1] >= fd->h ? fd->h - 1 : xy[1];
2753 }
2754 
2755 // OPT* widgets
2756 
2757 #if GTK_MAJOR_VERSION == 3
2758 
2759 /* !!! Limited to 256 choices max, by using id[0] for index */
opt_reset(void ** slot,char * ddata,int idx)2760 static void opt_reset(void **slot, char *ddata, int idx)
2761 {
2762 	void **pp = slot[1];
2763 	char **names, id[2] = { 0, 0 };
2764 	int i, j, k, cnt, opf = GET_OPF(slot), op = opf & WB_OPMASK;
2765 
2766 	if (op == op_OPTD) cnt = -1 , names = *(char ***)(ddata + (int)pp[2]);
2767 	else cnt = (int)pp[3] , names = pp[2];
2768 	if (!cnt) cnt = -1;
2769 
2770 	g_signal_handlers_disconnect_by_func(slot[0], get_evt_1, NEXT_SLOT(slot));
2771 	gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(slot[0]));
2772 	for (i = j = k = 0; (i != cnt) && names[i]; i++)
2773 	{
2774 		if (!names[i][0]) continue;
2775 		id[0] = i;
2776 		gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(slot[0]), id, _(names[i]));
2777 		if (i == idx) j = k;
2778 		k++;
2779   	}
2780 	gtk_combo_box_set_active(GTK_COMBO_BOX(slot[0]), j);
2781 	if (WB_GETREF(opf) > 1) g_signal_connect(slot[0], "changed",
2782 		G_CALLBACK(get_evt_1), NEXT_SLOT(slot));
2783 }
2784 
wj_option_menu_get_history(GtkWidget * cbox)2785 static int wj_option_menu_get_history(GtkWidget *cbox)
2786 {
2787 	const char *id = gtk_combo_box_get_active_id(GTK_COMBO_BOX(cbox));
2788 	return (id ? id[0] : 0);
2789 }
2790 
2791 #define wj_option_menu_set_history(W,N) gtk_combo_box_set_active(W,N)
2792 
2793 #else /* if GTK_MAJOR_VERSION <= 2 */
2794 
2795 #if GTK_MAJOR_VERSION == 2
2796 
2797 /* Cause the size to be properly reevaluated */
opt_size_fix(GtkWidget * widget)2798 static void opt_size_fix(GtkWidget *widget)
2799 {
2800 	gtk_signal_emit_by_name(GTK_OBJECT(gtk_option_menu_get_menu(
2801 		GTK_OPTION_MENU(widget))), "selection_done");
2802 }
2803 
2804 #endif
2805 
opt_reset(void ** slot,char * ddata,int idx)2806 static void opt_reset(void **slot, char *ddata, int idx)
2807 {
2808 	GtkWidget *menu, *item;
2809 	void **pp = slot[1];
2810 	char **names;
2811 	int i, j, k, cnt, opf = GET_OPF(slot), op = opf & WB_OPMASK;
2812 
2813 	if (op == op_OPTD) cnt = -1 , names = *(char ***)(ddata + (int)pp[2]);
2814 	else cnt = (int)pp[3] , names = pp[2];
2815 	if (!cnt) cnt = -1;
2816 
2817 	menu = gtk_menu_new();
2818 	for (i = j = k = 0; (i != cnt) && names[i]; i++)
2819 	{
2820 		if (!names[i][0]) continue;
2821 		item = gtk_menu_item_new_with_label(_(names[i]));
2822 		gtk_object_set_user_data(GTK_OBJECT(item), (gpointer)i);
2823 		if (WB_GETREF(opf) > 1) gtk_signal_connect(GTK_OBJECT(item),
2824 			"activate", GTK_SIGNAL_FUNC(get_evt_1), NEXT_SLOT(slot));
2825 		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
2826 		if (i == idx) j = k;
2827 		k++;
2828   	}
2829 	gtk_widget_show_all(menu);
2830 	gtk_menu_set_active(GTK_MENU(menu), j);
2831 	gtk_option_menu_set_menu(GTK_OPTION_MENU(slot[0]), menu);
2832 #if GTK_MAJOR_VERSION == 2
2833 	opt_size_fix(slot[0]);
2834 #endif
2835 }
2836 
wj_option_menu_get_history(GtkWidget * optmenu)2837 static int wj_option_menu_get_history(GtkWidget *optmenu)
2838 {
2839 	optmenu = gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu));
2840 	optmenu = gtk_menu_get_active(GTK_MENU(optmenu));
2841 	return (optmenu ? (int)gtk_object_get_user_data(GTK_OBJECT(optmenu)) : 0);
2842 }
2843 
2844 #define wj_option_menu_set_history(W,N) gtk_option_menu_set_history(W,N)
2845 
2846 #endif /* GTK+1&2 */
2847 
2848 //	RPACK* and COMBO widgets
2849 
mkpack(int mode,int d,int ref,char * ddata,void ** r)2850 static GtkWidget *mkpack(int mode, int d, int ref, char *ddata, void **r)
2851 {
2852 	void **pp = r[1];
2853 	char **src = pp[2];
2854 	int nh, n, v = *(int *)r[0];
2855 
2856 	nh = (n = (int)pp[3]) & 255;
2857 	if (mode) n >>= 8;
2858 	if (d) n = -1 , src = *(char ***)(ddata + (int)pp[2]);
2859 	if (!n) n = -1;
2860 	return ((mode ? wj_radio_pack : wj_combo_box)(src, n, nh, v,
2861 		NEXT_SLOT(r), ref < 2 ? NULL :
2862 		mode ? GTK_SIGNAL_FUNC(get_evt_1_t) : GTK_SIGNAL_FUNC(get_evt_1)));
2863 }
2864 
2865 #if GTK_MAJOR_VERSION == 3
2866 
col_expose(GtkWidget * widget,cairo_t * cr,unsigned char * col)2867 static gboolean col_expose(GtkWidget *widget, cairo_t *cr, unsigned char *col)
2868 {
2869 	cairo_save(cr);
2870 	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
2871 	cairo_set_rgb(cr, MEM_2_INT(col, 0));
2872 	cairo_paint(cr);
2873 	cairo_restore(cr);
2874 
2875 	return (TRUE);
2876 }
2877 
2878 #else /* if GTK_MAJOR_VERSION <= 2 */
2879 
2880 // !!! ref to RGB[3]
col_expose(GtkWidget * widget,GdkEventExpose * event,unsigned char * col)2881 static gboolean col_expose(GtkWidget *widget, GdkEventExpose *event,
2882 	unsigned char *col)
2883 {
2884 	GdkGCValues sv;
2885 
2886 	gdk_gc_get_values(widget->style->black_gc, &sv);
2887 	gdk_rgb_gc_set_foreground(widget->style->black_gc, MEM_2_INT(col, 0));
2888 	gdk_draw_rectangle(widget->window, widget->style->black_gc, TRUE,
2889 		event->area.x, event->area.y, event->area.width, event->area.height);
2890 	gdk_gc_set_foreground(widget->style->black_gc, &sv.foreground);
2891 
2892 	return (TRUE);
2893 }
2894 
2895 #endif /* GTK+1&2 */
2896 
2897 //	COLORLIST widget
2898 
2899 typedef struct {
2900 	unsigned char *col;
2901 	int cnt, *idx;
2902 } colorlist_data;
2903 
2904 #ifdef U_LISTS_GTK1
colorlist_click(GtkWidget * widget,GdkEventButton * event,gpointer user_data)2905 static gboolean colorlist_click(GtkWidget *widget, GdkEventButton *event,
2906 	gpointer user_data)
2907 {
2908 	void **slot = user_data;
2909 	void **base = slot[0], **desc = slot[1];
2910 	colorlist_ext xdata;
2911 
2912 	if (event->type == GDK_BUTTON_PRESS)
2913 	{
2914 		xdata.idx = (int)gtk_object_get_user_data(GTK_OBJECT(widget));
2915 		xdata.button = event->button;
2916 		((evtx_fn)desc[1])(GET_DDATA(base), base,
2917 			(int)desc[0] & WB_OPMASK, slot, &xdata);
2918 	}
2919 
2920 	/* Let click processing continue */
2921 	return (FALSE);
2922 }
2923 
colorlist_select(GtkList * list,GtkWidget * widget,gpointer user_data)2924 static void colorlist_select(GtkList *list, GtkWidget *widget, gpointer user_data)
2925 {
2926 	void **orig = user_data, **slot = SLOT_N(orig, 2);
2927 	void **base = slot[0], **desc = slot[1];
2928 	colorlist_data *dt = orig[2];
2929 
2930 	/* Update the value */
2931 	*dt->idx = (int)gtk_object_get_user_data(GTK_OBJECT(widget));
2932 	/* Call the handler */
2933 	if (desc[1]) ((evt_fn)desc[1])(GET_DDATA(base), base,
2934 		(int)desc[0] & WB_OPMASK, slot);
2935 }
2936 
2937 // !!! And with inlining this, problem also
colorlist(void ** r,char * ddata)2938 GtkWidget *colorlist(void **r, char *ddata)
2939 {
2940 	GtkWidget *list, *item, *col, *label, *box;
2941 	colorlist_data *dt = r[2];
2942 	void *v, **pp = r[1];
2943 	char txt[64], *t, **sp = NULL;
2944 	int i, cnt = 0, *idx = r[0];
2945 
2946 	list = gtk_list_new();
2947 
2948 	// Fill datablock
2949 	v = ddata + (int)pp[2];
2950 	if (((int)pp[0] & WB_OPMASK) == op_COLORLIST) // array of names
2951 	{
2952 		sp = *(char ***)v;
2953 		while (sp[cnt]) cnt++;
2954 	}
2955 	else cnt = *(int *)v; // op_COLORLISTN - number
2956 	dt->cnt = cnt;
2957 	dt->col = (void *)(ddata + (int)pp[3]); // palette
2958 	dt->idx = idx;
2959 
2960 	for (i = 0; i < cnt; i++)
2961 	{
2962 		item = gtk_list_item_new();
2963 		gtk_object_set_user_data(GTK_OBJECT(item), (gpointer)i);
2964 		if (pp[5]) gtk_signal_connect(GTK_OBJECT(item), "button_press_event",
2965 			GTK_SIGNAL_FUNC(colorlist_click), NEXT_SLOT(r));
2966 		gtk_container_add(GTK_CONTAINER(list), item);
2967 
2968 		box = gtk_hbox_new(FALSE, 3);
2969 		gtk_widget_show(box);
2970 		gtk_container_set_border_width(GTK_CONTAINER(box), 3);
2971 		gtk_container_add(GTK_CONTAINER(item), box);
2972 
2973 		col = pack(box, gtk_drawing_area_new());
2974 		gtk_drawing_area_size(GTK_DRAWING_AREA(col), 20, 20);
2975 		gtk_signal_connect(GTK_OBJECT(col), "expose_event",
2976 			GTK_SIGNAL_FUNC(col_expose), dt->col + i * 3);
2977 
2978 		/* Name or index */
2979 		if (sp) t = _(sp[i]);
2980 		else sprintf(t = txt, "%d", i);
2981 		label = xpack(box, gtk_label_new(t));
2982 		gtk_misc_set_alignment(GTK_MISC(label), 0.0, 1.0);
2983 
2984 		gtk_widget_show_all(item);
2985 	}
2986 	gtk_list_set_selection_mode(GTK_LIST(list), GTK_SELECTION_BROWSE);
2987 	/* gtk_list_select_*() don't work in GTK_SELECTION_BROWSE mode */
2988 	gtk_container_set_focus_child(GTK_CONTAINER(list),
2989 		GTK_WIDGET(g_list_nth(GTK_LIST(list)->children, *idx)->data));
2990 	gtk_signal_connect(GTK_OBJECT(list), "select_child",
2991 		GTK_SIGNAL_FUNC(colorlist_select), r);
2992 
2993 	return (list);
2994 }
2995 
colorlist_reset_color(void ** slot,int idx)2996 static void colorlist_reset_color(void **slot, int idx)
2997 {
2998 	colorlist_data *dt = slot[2];
2999 	unsigned char *rgb = dt->col + idx * 3;
3000 	GdkColor c;
3001 
3002 	c.pixel = 0;
3003 	c.red   = rgb[0] * 257;
3004 	c.green = rgb[1] * 257;
3005 	c.blue  = rgb[2] * 257;
3006 	// In case of some really ancient system with indexed display mode
3007 	gdk_colormap_alloc_color(gdk_colormap_get_system(), &c, FALSE, TRUE);
3008 	/* Redraw the item displaying the color */
3009 	gtk_widget_queue_draw(
3010 		GTK_WIDGET(g_list_nth(GTK_LIST(slot[0])->children, idx)->data));
3011 }
3012 
list_scroll_in(GtkWidget * widget,gpointer user_data)3013 static void list_scroll_in(GtkWidget *widget, gpointer user_data)
3014 {
3015 	GtkContainer *list = GTK_CONTAINER(widget);
3016 	GtkAdjustment *adj = user_data;
3017 	int y;
3018 
3019 	if (!list->focus_child) return; // Paranoia
3020 	if (adj->upper <= adj->page_size) return; // Nothing to scroll
3021 	y = list->focus_child->allocation.y +
3022 		list->focus_child->allocation.height / 2 -
3023 		adj->page_size / 2;
3024 	adj->value = y < 0 ? 0 : y > adj->upper - adj->page_size ?
3025 		adj->upper - adj->page_size : y;
3026 	gtk_adjustment_value_changed(adj);
3027 }
3028 #endif
3029 
3030 #ifndef U_LISTS_GTK1
colorlist_click(GtkWidget * widget,GdkEventButton * event,gpointer user_data)3031 static gboolean colorlist_click(GtkWidget *widget, GdkEventButton *event,
3032 	gpointer user_data)
3033 {
3034 	void **slot = user_data;
3035 	void **base = slot[0], **desc = slot[1];
3036 	colorlist_ext xdata;
3037 	GtkTreePath *tp;
3038 
3039 	if ((event->type == GDK_BUTTON_PRESS) &&
3040 		(event->window == gtk_tree_view_get_bin_window(GTK_TREE_VIEW(widget))) &&
3041 		gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), event->x, event->y,
3042 			&tp, NULL, NULL, NULL))
3043 	{
3044 		xdata.idx = gtk_tree_path_get_indices(tp)[0];
3045 		gtk_tree_path_free(tp);
3046 		xdata.button = event->button;
3047 		((evtx_fn)desc[1])(GET_DDATA(base), base,
3048 			(int)desc[0] & WB_OPMASK, slot, &xdata);
3049 	}
3050 
3051 	/* Let click processing continue */
3052 	return (FALSE);
3053 }
3054 
colorlist_select(GtkTreeView * tree,gpointer user_data)3055 static void colorlist_select(GtkTreeView *tree, gpointer user_data)
3056 {
3057 	void **orig = user_data, **slot = SLOT_N(orig, 2);
3058 	void **base = slot[0], **desc = slot[1];
3059 	colorlist_data *dt = orig[2];
3060 	GtkTreePath *tp;
3061 
3062 	/* Update the value */
3063 	gtk_tree_view_get_cursor(tree, &tp, NULL);
3064 	*dt->idx = gtk_tree_path_get_indices(tp)[0];
3065 	gtk_tree_path_free(tp);
3066 	/* Call the handler */
3067 	if (desc[1]) ((evt_fn)desc[1])(GET_DDATA(base), base,
3068 		(int)desc[0] & WB_OPMASK, slot);
3069 }
3070 
3071 // !!! And with inlining this, problem also
colorlist(void ** r,char * ddata)3072 GtkWidget *colorlist(void **r, char *ddata)
3073 {
3074 	GtkListStore *ls;
3075 	GtkCellRenderer *ren;
3076 	GtkTreePath *tp;
3077 	GtkWidget *w;
3078 	colorlist_data *dt = r[2];
3079 	void *v, **pp = r[1];
3080 	char txt[64], *t, **sp = NULL;
3081 	int i, cnt = 0, *idx = r[0];
3082 
3083 	// Fill datablock
3084 	v = ddata + (int)pp[2];
3085 	if (((int)pp[0] & WB_OPMASK) == op_COLORLIST) // array of names
3086 	{
3087 		sp = *(char ***)v;
3088 		while (sp[cnt]) cnt++;
3089 	}
3090 	else cnt = *(int *)v; // op_COLORLISTN - number
3091 	dt->cnt = cnt;
3092 	dt->col = (void *)(ddata + (int)pp[3]); // palette
3093 	dt->idx = idx;
3094 
3095 	ls = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
3096 	for (i = 0; i < cnt; i++)
3097 	{
3098 		GtkTreeIter it;
3099 		GdkPixbuf *p = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 20, 20);
3100 		gdk_pixbuf_fill(p, ((unsigned)MEM_2_INT(dt->col, i * 3) << 8) + 0xFF);
3101 		gtk_list_store_append(ls, &it);
3102 		/* Name or index */
3103 		if (sp) t = _(sp[i]);
3104 		else sprintf(t = txt, "%d", i);
3105 		gtk_list_store_set(ls, &it, 0, p, 1, t, -1);
3106 		g_object_unref(p);
3107 	}
3108 	w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls));
3109 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(w), FALSE);
3110 	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(w)),
3111 		GTK_SELECTION_BROWSE);
3112 	ren = gtk_cell_renderer_pixbuf_new();
3113 	g_object_set(ren, "width", 20 + 3, "xalign", 1.0, "ypad", 3, NULL);
3114 	gtk_tree_view_append_column(GTK_TREE_VIEW(w),
3115 		gtk_tree_view_column_new_with_attributes("Color", ren,
3116 			"pixbuf", 0, NULL));
3117 	ren = gtk_cell_renderer_text_new();
3118 	g_object_set(ren, "xalign", 0.0, "yalign", 1.0, "xpad", 3, "ypad", 3, NULL);
3119 	gtk_tree_view_append_column(GTK_TREE_VIEW(w),
3120 		gtk_tree_view_column_new_with_attributes("Index", ren,
3121 			"text", 1, NULL));
3122 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
3123 
3124 	tp = gtk_tree_path_new_from_indices(*idx, -1);
3125 	gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(w), tp, NULL, NULL, FALSE);
3126 	/* Seems safe to do w/o scrolledwindow too */
3127 	gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(w), tp, NULL, TRUE, 0.5, 0.0);
3128 	gtk_tree_path_free(tp);
3129 
3130 	if (pp[5]) g_signal_connect(w, "button_press_event", G_CALLBACK(colorlist_click), NEXT_SLOT(r));
3131 	g_signal_connect(w, "cursor_changed", G_CALLBACK(colorlist_select), r);
3132 
3133 	return (w);
3134 }
3135 
colorlist_reset_color(void ** slot,int idx)3136 static void colorlist_reset_color(void **slot, int idx)
3137 {
3138 	colorlist_data *dt = slot[2];
3139 	unsigned char *rgb = dt->col + idx * 3;
3140 	GtkTreeModel *tm = gtk_tree_view_get_model(GTK_TREE_VIEW(slot[0]));
3141 	GtkTreePath *tp = gtk_tree_path_new_from_indices(idx, -1);
3142 	GtkTreeIter it;
3143 
3144 	if (gtk_tree_model_get_iter(tm, &it, tp))
3145 	{
3146 		GdkPixbuf *p;
3147 		gtk_tree_model_get(tm, &it, 0, &p, -1);
3148 		gdk_pixbuf_fill(p, ((unsigned)MEM_2_INT(rgb, 0) << 8) + 0xFF);
3149 		g_object_unref(p); // !!! After get() ref'd it
3150 		/* Redraw the row displaying the color */
3151 		gtk_tree_model_row_changed(tm, tp, &it);
3152 	}
3153 	gtk_tree_path_free(tp);
3154 }
3155 #endif
3156 
3157 //	GRADBAR widget
3158 
3159 #define GRADBAR_LEN 16
3160 #define SLOT_SIZE 15
3161 
3162 typedef struct {
3163 	unsigned char *map, *rgb;
3164 	GtkWidget *lr[2];
3165 	void **r;
3166 	int ofs, lim, *idx, *len;
3167 	unsigned char idxs[GRADBAR_LEN];
3168 } gradbar_data;
3169 
gradbar_scroll(GtkWidget * btn,gpointer user_data)3170 static void gradbar_scroll(GtkWidget *btn, gpointer user_data)
3171 {
3172 	unsigned char *idx = user_data;
3173 	gradbar_data *dt = (void *)(idx - offsetof(gradbar_data, idxs) - *idx);
3174 	int dir = *idx * 2 - 1;
3175 
3176 	dt->ofs += dir;
3177 	*dt->idx += dir; // self-reading
3178 	gtk_widget_set_sensitive(dt->lr[0], !!dt->ofs);
3179 	gtk_widget_set_sensitive(dt->lr[1], dt->ofs < dt->lim - GRADBAR_LEN);
3180 	gtk_widget_queue_draw(gtk_widget_get_parent(btn));
3181 	get_evt_1(NULL, (gpointer)dt->r);
3182 }
3183 
gradbar_slot(GtkWidget * btn,gpointer user_data)3184 static void gradbar_slot(GtkWidget *btn, gpointer user_data)
3185 {
3186 	unsigned char *idx = user_data;
3187 	gradbar_data *dt;
3188 
3189 	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(btn))) return;
3190 	dt = (void *)(idx - offsetof(gradbar_data, idxs) - *idx);
3191 	*dt->idx = *idx + dt->ofs; // self-reading
3192 	get_evt_1(NULL, (gpointer)dt->r);
3193 }
3194 
3195 #if GTK_MAJOR_VERSION == 3
3196 
gradbar_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)3197 static gboolean gradbar_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
3198 {
3199 	unsigned char *rp, *idx = user_data;
3200 	gradbar_data *dt = (void *)(idx - offsetof(gradbar_data, idxs) - *idx);
3201 	int n = *idx + dt->ofs;
3202 
3203 	cairo_save(cr);
3204 	cairo_rectangle(cr, 0, 0, SLOT_SIZE, SLOT_SIZE);
3205 	if (n < *dt->len) // Filled slot
3206 	{
3207 		rp = dt->rgb ? dt->rgb + dt->map[n] * 3 : dt->map + n * 3;
3208 		cairo_set_rgb(cr, MEM_2_INT(rp, 0));
3209 
3210 	}
3211 	else // Empty slot - show that
3212 	{
3213 		cairo_set_rgb(cr, RGB_2_INT(178, 178, 178));
3214 		cairo_fill(cr);
3215 		cairo_set_rgb(cr, RGB_2_INT(128, 128, 128));
3216 		cairo_move_to(cr, 0, 0);
3217 		cairo_line_to(cr, 0, SLOT_SIZE);
3218 		cairo_line_to(cr, SLOT_SIZE, SLOT_SIZE);
3219 		cairo_close_path(cr);
3220 	}
3221 	cairo_fill(cr);
3222 	cairo_restore(cr);
3223 
3224 	return (TRUE);
3225 }
3226 
3227 #else /* if GTK_MAJOR_VERSION <= 2 */
3228 
gradbar_draw(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)3229 static gboolean gradbar_draw(GtkWidget *widget, GdkEventExpose *event,
3230 	gpointer user_data)
3231 {
3232 	unsigned char rgb[SLOT_SIZE * 2 * 3], *idx = user_data;
3233 	gradbar_data *dt = (void *)(idx - offsetof(gradbar_data, idxs) - *idx);
3234 	int i, n = *idx + dt->ofs;
3235 
3236 	if (n < *dt->len) // Filled slot
3237 	{
3238 		memcpy(rgb, dt->rgb ? dt->rgb + dt->map[n] * 3 :
3239 			dt->map + n * 3, 3);
3240 		for (i = 3; i < SLOT_SIZE * 2 * 3; i++) rgb[i] = rgb[i - 3];
3241 	}
3242 	else // Empty slot - show that
3243 	{
3244 		memset(rgb, 178, sizeof(rgb));
3245 		memset(rgb, 128, SLOT_SIZE * 3);
3246 	}
3247 
3248 	gdk_draw_rgb_image(widget->window, widget->style->black_gc,
3249 		0, 0, SLOT_SIZE, SLOT_SIZE, GDK_RGB_DITHER_NONE,
3250 		rgb + SLOT_SIZE * 3, -3);
3251 
3252 	return (TRUE);
3253 }
3254 
3255 #endif /* GTK+1&2 */
3256 
3257 // !!! With inlining this, problem also
gradbar(void ** r,char * ddata)3258 GtkWidget *gradbar(void **r, char *ddata)
3259 {
3260 	GtkWidget *hbox, *btn, *sw;
3261 	gradbar_data *dt = r[2];
3262 	void **pp = r[1];
3263 	int i;
3264 
3265 	hbox = gtk_hbox_new(TRUE, 0);
3266 
3267 	dt->r = NEXT_SLOT(r);
3268 	dt->idx = r[0];
3269 	dt->len = (void *)(ddata + (int)pp[3]); // length
3270 	dt->map = (void *)(ddata + (int)pp[4]); // gradient map
3271 	if (*(int *)(ddata + (int)pp[2])) // mode not-RGB
3272 		dt->rgb = (void *)(ddata + (int)pp[5]); // colormap
3273 	dt->lim = (int)pp[6];
3274 
3275 	dt->lr[0] = btn = xpack(hbox, gtk_button_new());
3276 	add_css_class(btn, "mtPaint_gradbar_button");
3277 	gtk_container_add(GTK_CONTAINER(btn), gtk_arrow_new(GTK_ARROW_LEFT,
3278 #if GTK_MAJOR_VERSION == 1
3279         // !!! Arrow w/o shadow is invisible in plain GTK+1
3280 		GTK_SHADOW_OUT));
3281 #else /* #if GTK_MAJOR_VERSION >= 2 */
3282 		GTK_SHADOW_NONE));
3283 #endif
3284 	gtk_widget_set_sensitive(btn, FALSE);
3285 	gtk_signal_connect(GTK_OBJECT(btn), "clicked",
3286 		GTK_SIGNAL_FUNC(gradbar_scroll), dt->idxs + 0);
3287 	btn = NULL;
3288 	for (i = 0; i < GRADBAR_LEN; i++)
3289 	{
3290 		dt->idxs[i] = i;
3291 		btn = xpack(hbox, gtk_radio_button_new_from_widget(
3292 			GTK_RADIO_BUTTON_0(btn)));
3293 		add_css_class(btn, "mtPaint_gradbar_button");
3294 		gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(btn), FALSE);
3295 		gtk_signal_connect(GTK_OBJECT(btn), "toggled",
3296 			GTK_SIGNAL_FUNC(gradbar_slot), dt->idxs + i);
3297 		sw = gtk_drawing_area_new();
3298 		gtk_container_add(GTK_CONTAINER(btn), sw);
3299 #if GTK_MAJOR_VERSION == 3
3300 		gtk_widget_set_valign(sw, GTK_ALIGN_CENTER);
3301 		gtk_widget_set_halign(sw, GTK_ALIGN_CENTER);
3302 		gtk_widget_set_size_request(sw, SLOT_SIZE, SLOT_SIZE);
3303 		g_signal_connect(G_OBJECT(sw), "draw",
3304 			G_CALLBACK(gradbar_draw), dt->idxs + i);
3305 #else /* if GTK_MAJOR_VERSION <= 2 */
3306 		gtk_widget_set_usize(sw, SLOT_SIZE, SLOT_SIZE);
3307 		gtk_signal_connect(GTK_OBJECT(sw), "expose_event",
3308 			GTK_SIGNAL_FUNC(gradbar_draw), dt->idxs + i);
3309 #endif /* GTK+1&2 */
3310 	}
3311 	dt->lr[1] = btn = xpack(hbox, gtk_button_new());
3312 	add_css_class(btn, "mtPaint_gradbar_button");
3313 	gtk_container_add(GTK_CONTAINER(btn), gtk_arrow_new(GTK_ARROW_RIGHT,
3314 #if GTK_MAJOR_VERSION == 1
3315         // !!! Arrow w/o shadow is invisible in plain GTK+1
3316 		GTK_SHADOW_OUT));
3317 #else /* #if GTK_MAJOR_VERSION >= 2 */
3318 		GTK_SHADOW_NONE));
3319 #endif
3320 	gtk_signal_connect(GTK_OBJECT(btn), "clicked",
3321 		GTK_SIGNAL_FUNC(gradbar_scroll), dt->idxs + 1);
3322 
3323 	gtk_widget_show_all(hbox);
3324 	return (hbox);
3325 }
3326 
3327 //	COMBOENTRY widget
3328 
3329 #if GTK_MAJOR_VERSION == 3
3330 
comboentry_reset(GtkWidget * cbox,char ** v,char ** src)3331 static void comboentry_reset(GtkWidget *cbox, char **v, char **src)
3332 {
3333 	GtkWidget *entry = gtk_bin_get_child(GTK_BIN(cbox));
3334 
3335 	gtk_entry_set_text(GTK_ENTRY(entry), *(char **)v);
3336 	// Replace transient buffer
3337 	*(const char **)v = gtk_entry_get_text(GTK_ENTRY(entry));
3338 
3339 	gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(cbox));
3340 	while (*src) gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cbox), *src++);
3341 }
3342 
comboentry_chg(GtkComboBox * cbox,gpointer user_data)3343 static void comboentry_chg(GtkComboBox *cbox, gpointer user_data)
3344 {
3345 	/* Only react to selecting from list */
3346 	if (gtk_combo_box_get_active(cbox) >= 0) get_evt_1(G_OBJECT(cbox), user_data);
3347 }
3348 
3349 // !!! With inlining this, problem also
comboentry(char * ddata,void ** r)3350 GtkWidget *comboentry(char *ddata, void **r)
3351 {
3352 	void **pp = r[1];
3353 	GtkWidget *cbox = gtk_combo_box_text_new_with_entry();
3354 	GtkWidget *entry = gtk_bin_get_child(GTK_BIN(cbox));
3355 
3356 	comboentry_reset(cbox, r[0], *(char ***)(ddata + (int)pp[2]));
3357 
3358 	g_signal_connect(G_OBJECT(cbox), "changed",
3359 		G_CALLBACK(comboentry_chg), NEXT_SLOT(r));
3360 	g_signal_connect(G_OBJECT(entry), "activate",
3361 		G_CALLBACK(get_evt_1), NEXT_SLOT(r));
3362 
3363 	return (cbox);
3364 }
3365 
3366 #define comboentry_get_text(A) \
3367 	gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(A))))
3368 #define comboentry_set_text(A,B) \
3369 	gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(A))), B)
3370 
3371 #else /* if GTK_MAJOR_VERSION <= 2 */
3372 
comboentry_reset(GtkCombo * combo,char ** v,char ** src)3373 static void comboentry_reset(GtkCombo *combo, char **v, char **src)
3374 {
3375 	GList *list = NULL;
3376 
3377 	gtk_entry_set_text(GTK_ENTRY(combo->entry), *(char **)v);
3378 	// Replace transient buffer
3379 	*(const char **)v = gtk_entry_get_text(GTK_ENTRY(combo->entry));
3380 	// NULL-terminated array of pointers
3381 	while (*src) list = g_list_append(list, *src++);
3382 #if GTK_MAJOR_VERSION == 1
3383 	if (!list) gtk_list_clear_items(GTK_LIST(combo->list), 0, -1);
3384 	else /* Fails if list is empty in GTK+1 */
3385 #endif
3386 	gtk_combo_set_popdown_strings(combo, list);
3387 	g_list_free(list);
3388 }
3389 
3390 // !!! With inlining this, problem also
comboentry(char * ddata,void ** r)3391 GtkWidget *comboentry(char *ddata, void **r)
3392 {
3393 	void **pp = r[1];
3394 	GtkWidget *w = gtk_combo_new();
3395 	GtkCombo *combo = GTK_COMBO(w);
3396 
3397 	gtk_combo_disable_activate(combo);
3398 	comboentry_reset(combo, r[0], *(char ***)(ddata + (int)pp[2]));
3399 
3400 	gtk_signal_connect(GTK_OBJECT(combo->popwin), "hide",
3401 		GTK_SIGNAL_FUNC(get_evt_1), NEXT_SLOT(r));
3402 	gtk_signal_connect(GTK_OBJECT(combo->entry), "activate",
3403 		GTK_SIGNAL_FUNC(get_evt_1), NEXT_SLOT(r));
3404 
3405 	return (w);
3406 }
3407 
3408 #define comboentry_get_text(A) gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(A)->entry))
3409 #define comboentry_set_text(A,B) gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(A)->entry), B)
3410 
3411 #endif /* GTK+1&2 */
3412 
3413 //	PCTCOMBO widget
3414 
3415 #if GTK_MAJOR_VERSION == 3
3416 
3417 // !!! Even with inlining this, some space gets wasted
pctcombo(void ** r)3418 GtkWidget *pctcombo(void **r)
3419 {
3420 	GtkWidget *cbox, *entry, *button;
3421 	char buf[32];
3422 	int i, n = 0, v = *(int *)r[0], *ns = GET_DESCV(r, 2);
3423 
3424 	/* Uses 0-terminated array of ints */
3425 	while (ns[n] > 0) n++;
3426 
3427 	cbox = gtk_combo_box_text_new_with_entry();
3428 	/* Find the button */
3429 	button = combobox_button(cbox);
3430 	gtk_widget_set_can_focus(button, FALSE);
3431 	/* Make it small enough */
3432 	css_restyle(button, ".mtPaint_pctbutton { padding: 0; }",
3433 		"mtPaint_pctbutton", NULL);
3434 	gtk_widget_set_size_request(button, 18, -1);
3435 
3436 	entry = gtk_bin_get_child(GTK_BIN(cbox));
3437 	gtk_widget_set_can_focus(entry, FALSE);
3438 	gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
3439 	gtk_entry_set_width_chars(GTK_ENTRY(entry), 6);
3440 	gtk_entry_set_max_width_chars(GTK_ENTRY(entry), 6);
3441 
3442 	for (i = 0; i < n; i++)
3443 	{
3444 		sprintf(buf, "%d%%", ns[i]);
3445 		gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cbox), buf);
3446 	}
3447 	sprintf(buf, "%d%%", v);
3448 	gtk_entry_set_text(GTK_ENTRY(entry), buf);
3449 
3450 	g_signal_connect(G_OBJECT(entry), "changed",
3451 		G_CALLBACK(get_evt_1), NEXT_SLOT(r));
3452 
3453 	return (cbox);
3454 }
3455 
3456 #define pctcombo_entry(A) gtk_bin_get_child(GTK_BIN(A))
3457 
3458 #else /* if GTK_MAJOR_VERSION <= 2 */
3459 
3460 #if (GTK_MAJOR_VERSION == 2) && (GTK2VERSION < 12) /* GTK+ 2.10 or earlier */
3461 
pctcombo_chg(GtkWidget * entry,gpointer user_data)3462 static void pctcombo_chg(GtkWidget *entry, gpointer user_data)
3463 {
3464 	/* Filter out spurious deletions */
3465 	if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))))
3466 		get_evt_1(NULL, user_data);
3467 }
3468 
3469 #endif
3470 
3471 // !!! Even with inlining this, some space gets wasted
pctcombo(void ** r)3472 GtkWidget *pctcombo(void **r)
3473 {
3474 	GtkWidget *w, *entry;
3475 	GList *list = NULL;
3476 	char *ts, *s;
3477 	int i, n = 0, v = *(int *)r[0], *ns = GET_DESCV(r, 2);
3478 
3479 	/* Uses 0-terminated array of ints */
3480 	while (ns[n] > 0) n++;
3481 
3482 	w = gtk_combo_new();
3483 	gtk_combo_set_value_in_list(GTK_COMBO(w), FALSE, FALSE);
3484 	entry = GTK_COMBO(w)->entry;
3485 	GTK_WIDGET_UNSET_FLAGS(entry, GTK_CAN_FOCUS);
3486 	gtk_widget_set_usize(GTK_COMBO(w)->button, 18, -1);
3487 #if GTK_MAJOR_VERSION == 1
3488 	gtk_widget_set_usize(w, 75, -1);
3489 #else /* #if GTK_MAJOR_VERSION == 2 */
3490 	gtk_entry_set_width_chars(GTK_ENTRY(entry), 6);
3491 #endif
3492 	gtk_entry_set_editable(GTK_ENTRY(entry), FALSE);
3493 
3494 	ts = s = calloc(n, 32); // 32 chars per int
3495 	for (i = 0; i < n; i++)
3496 	{
3497 		list = g_list_append(list, s);
3498 		s += sprintf(s, "%d%%", ns[i]) + 1;
3499 	}
3500 	gtk_combo_set_popdown_strings(GTK_COMBO(w), list);
3501 	g_list_free(list);
3502 	sprintf(ts, "%d%%", v);
3503 	gtk_entry_set_text(GTK_ENTRY(entry), ts);
3504 	free(ts);
3505 
3506 	/* In GTK1, combo box entry is updated continuously */
3507 #if GTK_MAJOR_VERSION == 1
3508 	gtk_signal_connect(GTK_OBJECT(GTK_COMBO(w)->popwin), "hide",
3509 		GTK_SIGNAL_FUNC(get_evt_1), NEXT_SLOT(r));
3510 #elif GTK2VERSION < 12 /* GTK+ 2.10 or earlier */
3511 	gtk_signal_connect(GTK_OBJECT(entry), "changed",
3512 		GTK_SIGNAL_FUNC(pctcombo_chg), NEXT_SLOT(r));
3513 #else /* GTK+ 2.12+ */
3514 	gtk_signal_connect(GTK_OBJECT(entry), "changed",
3515 		GTK_SIGNAL_FUNC(get_evt_1), NEXT_SLOT(r));
3516 #endif
3517 
3518 	return (w);
3519 }
3520 
3521 #define pctcombo_entry(A) (GTK_COMBO(A)->entry)
3522 
3523 #endif /* GTK+1&2 */
3524 
3525 //	TEXT widget
3526 
textarea(char * init)3527 static GtkWidget *textarea(char *init)
3528 {
3529 	GtkWidget *scroll, *text;
3530 
3531 #if GTK_MAJOR_VERSION == 1
3532 	text = gtk_text_new(NULL, NULL);
3533 	gtk_text_set_editable(GTK_TEXT(text), TRUE);
3534 	if (init) gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL, init, -1);
3535 
3536 	scroll = gtk_scrolled_window_new(NULL, GTK_TEXT(text)->vadj);
3537 #else /* #if GTK_MAJOR_VERSION >= 2 */
3538 	GtkTextBuffer *texbuf = gtk_text_buffer_new(NULL);
3539 	if (init) gtk_text_buffer_set_text(texbuf, init, -1);
3540 
3541 	text = gtk_text_view_new_with_buffer(texbuf);
3542 
3543 #if GTK_MAJOR_VERSION == 3
3544 	scroll = gtk_scrolled_window_new(
3545 		gtk_scrollable_get_hadjustment(GTK_SCROLLABLE(text)),
3546 		gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(text)));
3547 #else
3548 	scroll = gtk_scrolled_window_new(GTK_TEXT_VIEW(text)->hadjustment,
3549 		GTK_TEXT_VIEW(text)->vadjustment);
3550 #endif
3551 #endif
3552 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
3553 		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
3554 	gtk_container_add(GTK_CONTAINER(scroll), text);
3555 	gtk_widget_show_all(scroll);
3556 
3557 	return (text);
3558 }
3559 
read_textarea(GtkWidget * text)3560 static char *read_textarea(GtkWidget *text)
3561 {
3562 #if GTK_MAJOR_VERSION == 1
3563 	return (gtk_editable_get_chars(GTK_EDITABLE(text), 0, -1));
3564 #else /* #if GTK_MAJOR_VERSION >= 2 */
3565 	GtkTextIter begin, end;
3566 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
3567 
3568 	gtk_text_buffer_get_start_iter(buffer, &begin);
3569 	gtk_text_buffer_get_end_iter(buffer, &end);
3570 	return (gtk_text_buffer_get_text(buffer, &begin, &end, TRUE));
3571 #endif
3572 }
3573 
set_textarea(GtkWidget * text,char * init)3574 static void set_textarea(GtkWidget *text, char *init)
3575 {
3576 #if GTK_MAJOR_VERSION == 1
3577 	gtk_editable_delete_text(GTK_EDITABLE(text), 0, -1);
3578 	if (init) gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL, init, -1);
3579 #else /* #if GTK_MAJOR_VERSION >= 2 */
3580 	gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)),
3581 		init ? init : "", -1);
3582 #endif
3583 }
3584 
3585 //	Columns for LIST* widgets
3586 
3587 typedef struct {
3588 	char *ddata;			// datastruct
3589 	void **dcolumn;			// datablock "column" if any
3590 	void **columns[MAX_COLS];	// column slots
3591 	void **r;			// slot
3592 	int ncol;			// columns
3593 } col_data;
3594 
get_cell(col_data * c,int row,int col)3595 static void *get_cell(col_data *c, int row, int col)
3596 {
3597 	void **cp = c->columns[col][1];
3598 	char *v;
3599 	int op, kind, ofs = 0;
3600 
3601 	kind = (int)cp[3] >> COL_LSHIFT;
3602 	if (!cp[2]) // relative
3603 	{
3604 		ofs = (int)cp[1];
3605 		cp = c->dcolumn;
3606 	}
3607 	op = (int)cp[0];
3608 	v = cp[1];
3609 	if (op & WB_FFLAG) v = c->ddata + (int)v;
3610 	if (op & WB_NFLAG) v = *(void **)v; // array dereference
3611 	if (!v) return (NULL); // Paranoia
3612 	v += ofs + row * (int)cp[2];
3613 	if (kind == col_PTR) v = *(char **)v; // cell dereference
3614 	else if (kind == col_REL) v += *(int *)v;
3615 
3616 	return (v);
3617 }
3618 
set_columns(col_data * c,col_data * src,char * ddata,void ** r)3619 static void set_columns(col_data *c, col_data *src, char *ddata, void **r)
3620 {
3621 	int i;
3622 
3623 	/* Copy & clear the source */
3624 	*c = *src;
3625 	memset(src, 0, sizeof(*src));
3626 
3627 	c->ddata = ddata;
3628 	c->r = r;
3629 	/* Link columns to group */
3630 	for (i = 0; i < c->ncol; i++)
3631 		((swdata *)c->columns[i][0])->strs = (void *)c;
3632 }
3633 
3634 //	LISTCC widget
3635 
3636 typedef struct {
3637 	int lock;		// against in-reset signals
3638 	int wantfoc;		// delayed focus
3639 	int h;			// max height
3640 	int *idx;		// result field
3641 	int *cnt;		// length field
3642 	col_data c;
3643 } listcc_data;
3644 
3645 #ifdef U_LISTS_GTK1
listcc_select(GtkList * list,GtkWidget * widget,gpointer user_data)3646 static void listcc_select(GtkList *list, GtkWidget *widget, gpointer user_data)
3647 {
3648 	listcc_data *dt = user_data;
3649 	void **base, **desc, **slot = NEXT_SLOT(dt->c.r);
3650 
3651 	if (dt->lock) return;
3652 	/* Update the value */
3653 	*dt->idx = (int)gtk_object_get_user_data(GTK_OBJECT(widget));
3654 	// !!! No touching slot outside lock: uninitialized the first time
3655 	base = slot[0]; desc = slot[1];
3656 	/* Call the handler */
3657 	if (desc[1]) ((evt_fn)desc[1])(GET_DDATA(base), base,
3658 		(int)desc[0] & WB_OPMASK, slot);
3659 }
3660 
listcc_select_item(void ** slot)3661 static void listcc_select_item(void **slot)
3662 {
3663 	GtkWidget *item, *win, *list = slot[0], *fw = NULL;
3664 	listcc_data *dt = slot[2];
3665 	int idx = *dt->idx, n = dt->h - idx - 1; // backward
3666 
3667 	if (!(item = g_list_nth_data(GTK_LIST(list)->children, n))) return;
3668 	/* Move focus if sensitive, flag to be moved later if not */
3669 	if (!(dt->wantfoc = !GTK_WIDGET_IS_SENSITIVE(list)))
3670 	{
3671 		dt->lock++; // this is strictly a visual update
3672 
3673 		win = gtk_widget_get_toplevel(list);
3674 		if (GTK_IS_WINDOW(win)) fw = GTK_WINDOW(win)->focus_widget;
3675 
3676 		/* Focus is somewhere in list - move it, selection will follow */
3677 		if (fw && gtk_widget_is_ancestor(fw, list))
3678 			gtk_widget_grab_focus(item);
3679 		else /* Focus is elsewhere - move whatever remains, then */
3680 		{
3681 	/* !!! For simplicity, an undocumented field is used; a bit less hacky
3682 	 * but longer is to set focus child to item, NULL, and item again - WJ */
3683 			gtk_container_set_focus_child(GTK_CONTAINER(list), item);
3684 			GTK_LIST(list)->last_focus_child = item;
3685 		}
3686 		/* Clear stuck selections when possible */
3687 		gtk_list_item_select(GTK_LIST_ITEM(item));
3688 
3689 		dt->lock--;
3690 	}
3691 	/* Signal must be reliable even with focus delayed */
3692 	listcc_select(NULL, item, dt);
3693 }
3694 
listcc_update(GtkWidget * widget,GtkStateType state,gpointer user_data)3695 static void listcc_update(GtkWidget *widget, GtkStateType state, gpointer user_data)
3696 {
3697 	if (state == GTK_STATE_INSENSITIVE)
3698 	{
3699 		void **slot = user_data;
3700 		listcc_data *dt = slot[2];
3701 
3702 		if (!dt->wantfoc) return;
3703 		dt->lock++; // strictly a visual update
3704 		listcc_select_item(slot);
3705 		dt->lock--;
3706 	}
3707 }
3708 
listcc_toggled(GtkWidget * widget,gpointer user_data)3709 static void listcc_toggled(GtkWidget *widget, gpointer user_data)
3710 {
3711 	listcc_data *dt = user_data;
3712 	void **slot, **base, **desc;
3713 	char *v;
3714 	int col, row;
3715 
3716 	if (dt->lock) return;
3717 	/* Find out what happened to what, and where */
3718 	col = (int)gtk_object_get_user_data(GTK_OBJECT(widget));
3719 	row = (int)gtk_object_get_user_data(GTK_OBJECT(widget->parent->parent));
3720 
3721 	/* Self-updating */
3722 	v = get_cell(&dt->c, row, col);
3723 	*(int *)v = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
3724 	/* Now call the handler */
3725 	slot = dt->c.columns[col];
3726 	slot = NEXT_SLOT(slot);
3727 	base = slot[0]; desc = slot[1];
3728 	if (desc[1]) ((evtx_fn)desc[1])(GET_DDATA(base), base,
3729 		(int)desc[0] & WB_OPMASK, slot, (void *)row);
3730 }
3731 
listcc_reset(void ** slot,int row)3732 static void listcc_reset(void **slot, int row)
3733 {
3734 	GtkWidget *list = slot[0];
3735 	GList *col, *curr = GTK_LIST(list)->children;
3736 	listcc_data *ld = slot[2];
3737 	int cnt = *ld->cnt;
3738 
3739 
3740 	if (row >= 0) // specific row, not whole list
3741 		curr = g_list_nth(curr, ld->h - row - 1); // backward
3742 	ld->lock = TRUE;
3743 	for (; curr; curr = curr->next)
3744 	{
3745 		GtkWidget *item = curr->data;
3746 		int j, n = (int)gtk_object_get_user_data(GTK_OBJECT(item));
3747 
3748 		if (n >= cnt)
3749 		{
3750 			gtk_widget_hide(item);
3751 			/* !!! To stop arrow keys handler from selecting this */
3752 			gtk_widget_set_sensitive(item, FALSE);
3753 			continue;
3754 		}
3755 
3756 		col = GTK_BOX(GTK_BIN(item)->child)->children;
3757 		for (j = 0; j < ld->c.ncol; col = col->next , j++)
3758 		{
3759 			GtkWidget *widget = ((GtkBoxChild *)(col->data))->widget;
3760 			char *v = get_cell(&ld->c, n, j);
3761 			void **cp = ld->c.columns[j][1];
3762 			int op = (int)cp[0] & WB_OPMASK;
3763 
3764 			if ((op == op_TXTCOLUMN) || (op == op_XTXTCOLUMN))
3765 				gtk_label_set_text(GTK_LABEL(widget), v);
3766 			else if (op == op_CHKCOLUMN)
3767 				gtk_toggle_button_set_active(
3768 					GTK_TOGGLE_BUTTON(widget), *(int *)v);
3769 		}
3770 		gtk_widget_set_sensitive(item, TRUE);
3771 		gtk_widget_show(item);
3772 		if (row >= 0) break; // one row only
3773 	}
3774 
3775 	if (row < 0) listcc_select_item(slot); // only when full reset
3776 	ld->lock = FALSE;
3777 }
3778 
3779 // !!! With inlining this, problem also
listcc(void ** r,char * ddata,col_data * c)3780 GtkWidget *listcc(void **r, char *ddata, col_data *c)
3781 {
3782 	char txt[128];
3783 	GtkWidget *widget, *list, *item, *uninit_(hbox);
3784 	listcc_data *ld = r[2];
3785 	void **pp = r[1];
3786 	int j, n, h, *cnt, *idx = r[0];
3787 
3788 
3789 	cnt = (void *)(ddata + (int)pp[2]); // length pointer
3790 	h = (int)pp[3]; // max for variable length
3791 	if (h < *cnt) h = *cnt;
3792 
3793 	list = gtk_list_new();
3794 	gtk_list_set_selection_mode(GTK_LIST(list), GTK_SELECTION_BROWSE);
3795 
3796 	/* Fill datastruct */
3797 	ld->idx = idx;
3798 	ld->cnt = cnt;
3799 	ld->h = h;
3800 	set_columns(&ld->c, c, ddata, r);
3801 
3802 	for (n = h - 1; n >= 0; n--) // Reverse numbering
3803 	{
3804 		item = gtk_list_item_new();
3805 		gtk_object_set_user_data(GTK_OBJECT(item), (gpointer)n);
3806 		gtk_container_add(GTK_CONTAINER(list), item);
3807 // !!! Spacing = 3
3808 		hbox = gtk_hbox_new(FALSE, 3);
3809 		gtk_container_add(GTK_CONTAINER(item), hbox);
3810 
3811 		for (j = 0; j < ld->c.ncol; j++)
3812 		{
3813 			void **cp = ld->c.columns[j][1];
3814 			int op = (int)cp[0] & WB_OPMASK, jw = (int)cp[3];
3815 
3816 			if (op == op_CHKCOLUMN)
3817 			{
3818 #if GTK_MAJOR_VERSION == 1
3819 			/* !!! Vertical spacing is too small without the label */
3820 				widget = gtk_check_button_new_with_label("");
3821 #else /* if GTK_MAJOR_VERSION == 2 */
3822 			/* !!! Focus frame is placed wrong with the empty label */
3823 				widget = gtk_check_button_new();
3824 #endif
3825 				gtk_object_set_user_data(GTK_OBJECT(widget),
3826 					(gpointer)j);
3827 				gtk_signal_connect(GTK_OBJECT(widget), "toggled",
3828 					GTK_SIGNAL_FUNC(listcc_toggled), ld);
3829 			}
3830 			else
3831 			{
3832 				if ((op == op_TXTCOLUMN) || (op == op_XTXTCOLUMN))
3833 					txt[0] = '\0'; // Init to empty string
3834 				else /* if (op == op_IDXCOLUMN) */ // Constant
3835 					sprintf(txt, "%d", (int)cp[1] + (int)cp[2] * n);
3836 				widget = gtk_label_new(txt);
3837 				gtk_misc_set_alignment(GTK_MISC(widget),
3838 					((jw >> 16) & 3) * 0.5, 0.5);
3839 			}
3840 			if (jw & 0xFFFF)
3841 				gtk_widget_set_usize(widget, jw & 0xFFFF, -2);
3842 			(op == op_XTXTCOLUMN ? xpack : pack)(hbox, widget);
3843 		}
3844 		gtk_widget_show_all(hbox);
3845 	}
3846 
3847 	r[0] = list; // Fix up slot
3848 	if (*cnt) listcc_reset(r, -1);
3849 
3850 	gtk_signal_connect(GTK_OBJECT(list), "select_child",
3851 		GTK_SIGNAL_FUNC(listcc_select), ld);
3852 	/* To move focus when delayed by insensitivity */
3853 	widget = pack(hbox, gtk_label_new("")); // canary: updated last
3854 	gtk_signal_connect(GTK_OBJECT(widget), "state_changed",
3855 		GTK_SIGNAL_FUNC(listcc_update), r);
3856 
3857 	return (list);
3858 }
3859 #endif
3860 
3861 #ifndef U_LISTS_GTK1
3862 
3863 #define LISTCC_KEY "mtPaint.listcc"
3864 
listcc_select(GtkTreeView * tree,gpointer user_data)3865 static void listcc_select(GtkTreeView *tree, gpointer user_data)
3866 {
3867 	listcc_data *dt = user_data;
3868 	void **base, **desc, **slot = NEXT_SLOT(dt->c.r);
3869 	GtkTreePath *tp;
3870 	int l;
3871 
3872 	if (dt->lock) return;
3873 	/* Update the value */
3874 	l = gtk_tree_model_iter_n_children(gtk_tree_view_get_model(tree), NULL);
3875 	gtk_tree_view_get_cursor(tree, &tp, NULL);
3876 	*dt->idx = l - 1 - gtk_tree_path_get_indices(tp)[0]; // Backward
3877 	gtk_tree_path_free(tp);
3878 
3879 	base = slot[0]; desc = slot[1];
3880 	/* Call the handler */
3881 	if (desc[1]) ((evt_fn)desc[1])(GET_DDATA(base), base,
3882 		(int)desc[0] & WB_OPMASK, slot);
3883 }
3884 
listcc_select_item(void ** slot)3885 static void listcc_select_item(void **slot)
3886 {
3887 	GtkTreeView *tr = slot[0];
3888 	GtkTreeModel *tm = gtk_tree_view_get_model(tr);
3889 	GtkTreePath *tp;
3890 	listcc_data *dt = slot[2];
3891 	int l, idx = *dt->idx;
3892 
3893 	l = gtk_tree_model_iter_n_children(tm, NULL);
3894 	if ((idx < 0) || (idx >= l)) return;
3895 	dt->lock++; // this is strictly a visual update
3896 	tp = gtk_tree_path_new_from_indices(l - idx - 1, -1); // backward
3897 	gtk_tree_view_set_cursor_on_cell(tr, tp, NULL, NULL, FALSE);
3898 	gtk_tree_path_free(tp);
3899 	dt->lock--;
3900 
3901 	/* Signal must be reliable whatever happens */
3902 	listcc_select(tr, dt);
3903 }
3904 
listcc_toggled(GtkCellRendererToggle * ren,gchar * path,gpointer user_data)3905 void listcc_toggled(GtkCellRendererToggle *ren, gchar *path, gpointer user_data)
3906 {
3907 	void **slot = g_object_get_data(G_OBJECT(ren), LISTCC_KEY);
3908 	void **base, **desc;
3909 	listcc_data *dt = slot[2];
3910 	GtkTreeView *tr = slot[0];
3911 	GtkTreeModel *tm;
3912 	GtkTreeIter it;
3913 	gint yf;
3914 	char *v;
3915 	int col, row;
3916 
3917 	if (dt->lock) return;
3918 	/* Find out what happened to what, and where */
3919 	tm = gtk_tree_view_get_model(tr);
3920 	if (!gtk_tree_model_get_iter_from_string(tm, &it, path)) return; // Paranoia
3921 	col = (int)user_data;
3922 	gtk_tree_model_get(tm, &it, col, &yf, -1);
3923 	gtk_list_store_set(GTK_LIST_STORE(tm), &it, col, yf ^= 1, -1); // Update list
3924 	row = yf >> 1;
3925 
3926 	/* Self-updating */
3927 	v = get_cell(&dt->c, row, col);
3928 	*(int *)v = yf & 1;
3929 	/* Now call the handler */
3930 	slot = dt->c.columns[col];
3931 	slot = NEXT_SLOT(slot);
3932 	base = slot[0]; desc = slot[1];
3933 	if (desc[1]) ((evtx_fn)desc[1])(GET_DDATA(base), base,
3934 		(int)desc[0] & WB_OPMASK, slot, (void *)row);
3935 }
3936 
listcc_reset(void ** slot,int row)3937 static void listcc_reset(void **slot, int row)
3938 {
3939 	GtkTreeView *tree = GTK_TREE_VIEW(slot[0]);
3940 	listcc_data *ld = slot[2];
3941 	GtkListStore *ls;
3942 	GtkTreeIter it;
3943 	char txt[64];
3944 	int j, n = 0, ncol = ld->c.ncol, cnt = *ld->cnt;
3945 
3946 	ld->lock = TRUE;
3947 	if (row >= 0) // specific row, not whole list
3948 	{
3949 		GtkTreePath *tp;
3950 		GtkTreeModel *tm = gtk_tree_view_get_model(tree);
3951 		int l = gtk_tree_model_iter_n_children(tm, NULL);
3952 
3953 		ls = GTK_LIST_STORE(tm);
3954 #if 0 /* LISTCC_RESET_ROW currently is NOT used on added/removed ones */
3955 	/* Add/remove existing rows; expecting one row only so let updates be */
3956 		if ((row >= cnt) && (row < l))
3957 		{
3958 			gtk_tree_model_get_iter_first(tm, &it);
3959 			while (row < l--) gtk_list_store_remove(ls, &it);
3960 		}
3961 		else if ((row < cnt) && (row >= l))
3962 		{
3963 			while (row >= l++) gtk_list_store_prepend(ls, &it);
3964 		}
3965 #endif
3966 		tp = gtk_tree_path_new_from_indices(l - 1 - row, -1);
3967 		if (!gtk_tree_model_get_iter(tm, &it, tp))
3968 			cnt = 0; // !!! Row out of range, do nothing
3969 		gtk_tree_path_free(tp);
3970 		n = row;
3971 	}
3972 	else // prepare new model
3973 	{
3974 		GType ctypes[MAX_COLS];
3975 
3976 		for (j = 0; j < ncol; j++)
3977 		{
3978 			void **cp = ld->c.columns[j][1];
3979 			int op = (int)cp[0] & WB_OPMASK;
3980 			ctypes[j] = op == op_CHKCOLUMN ? G_TYPE_INT : G_TYPE_STRING;
3981 		}
3982 		ls = gtk_list_store_newv(ncol, ctypes);
3983 	}
3984 
3985 	for (; n < cnt; n++)
3986 	{
3987 		if (row < 0) gtk_list_store_prepend(ls, &it);
3988 		for (j = 0; j < ncol; j++)
3989 		{
3990 			char *v = get_cell(&ld->c, n, j);
3991 			void **cp = ld->c.columns[j][1];
3992 			int op = (int)cp[0] & WB_OPMASK;
3993 
3994 			if (op == op_CHKCOLUMN) // index * 2 + flag
3995 				gtk_list_store_set(ls, &it, j, n * 2 + !!*(int *)v, -1);
3996 			else
3997 			{
3998 				if (op == op_IDXCOLUMN) // Constant
3999 					sprintf(v = txt, "%d", (int)cp[1] + (int)cp[2] * n);
4000 				// op_TXTCOLUMN/op_XTXTCOLUMN otherwise
4001 				gtk_list_store_set(ls, &it, j, v, -1);
4002 			}
4003 		}
4004 		if (row >= 0) break; // one row only
4005 	}
4006 
4007 	if (row < 0)
4008 	{
4009 		gtk_tree_view_set_model(tree, GTK_TREE_MODEL(ls));
4010 		listcc_select_item(slot); // only when full reset
4011 
4012 		/* !!! Sometimes it shows the wrong part and redraw doesn't help */
4013 		gtk_adjustment_value_changed(gtk_tree_view_get_vadjustment(tree));
4014 	}
4015 	ld->lock = FALSE;
4016 }
4017 
listcc_chk(GtkTreeViewColumn * col,GtkCellRenderer * ren,GtkTreeModel * tm,GtkTreeIter * it,gpointer data)4018 static void listcc_chk(GtkTreeViewColumn *col, GtkCellRenderer *ren,
4019 	GtkTreeModel *tm, GtkTreeIter *it, gpointer data)
4020 {
4021 	gint yf;
4022 
4023 	gtk_tree_model_get(tm, it, (int)data, &yf, -1);
4024 	g_object_set(ren, "active", yf & 1, NULL);
4025 }
4026 
listcc_scroll_in(GtkWidget * widget,gpointer user_data)4027 static void listcc_scroll_in(GtkWidget *widget, gpointer user_data)
4028 {
4029 	GtkTreePath *tp;
4030 
4031 	gtk_tree_view_get_cursor(GTK_TREE_VIEW(widget), &tp, NULL);
4032 	if (!tp) return; // Paranoia
4033 	gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(widget), tp, NULL, FALSE, 0, 0);
4034 	gtk_tree_path_free(tp);
4035 }
4036 
4037 // !!! With inlining this, problem also
listcc(void ** r,char * ddata,col_data * c)4038 GtkWidget *listcc(void **r, char *ddata, col_data *c)
4039 {
4040 	GtkWidget *w;
4041 	listcc_data *ld = r[2];
4042 	void **pp = r[1];
4043 	int j, h, *cnt, *idx = r[0];
4044 
4045 
4046 	cnt = (void *)(ddata + (int)pp[2]); // length pointer
4047 	h = (int)pp[3]; // max for variable length
4048 	if (h < *cnt) h = *cnt;
4049 
4050 	/* Fill datastruct */
4051 	ld->idx = idx;
4052 	ld->cnt = cnt;
4053 	ld->h = h;
4054 	set_columns(&ld->c, c, ddata, r);
4055 
4056 	w = gtk_tree_view_new();
4057 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(w), FALSE);
4058 	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(w)),
4059 		GTK_SELECTION_BROWSE);
4060 	for (j = 0; j < ld->c.ncol; j++)
4061 	{
4062 		GtkCellRenderer *ren;
4063 		GtkTreeViewColumn *col;
4064 		void **cp = ld->c.columns[j][1];
4065 		int op = (int)cp[0] & WB_OPMASK, jw = (int)cp[3];
4066 
4067 		if (op == op_CHKCOLUMN)
4068 		{
4069 			ren = gtk_cell_renderer_toggle_new();
4070 			/* To preserve spacing, need a height of a GtkLabel here;
4071 			 * but waiting for realize to get & set it is a hassle,
4072 			 * and the layers box looks perfectly OK as is - WJ */
4073 //			g_object_set(ren, "height", 10, NULL);
4074 			col = gtk_tree_view_column_new_with_attributes("", ren, NULL);
4075 			gtk_tree_view_column_set_cell_data_func(col, ren,
4076 				listcc_chk, (gpointer)j, NULL);
4077 			g_object_set_data(G_OBJECT(ren), LISTCC_KEY, r);
4078 			g_signal_connect(ren, "toggled",
4079 				G_CALLBACK(listcc_toggled), (gpointer)j);
4080 		}
4081 		else /* op_TXTCOLUMN/op_XTXTCOLUMN/op_IDXCOLUMN */
4082 		{
4083 			ren = gtk_cell_renderer_text_new();
4084 			gtk_cell_renderer_set_alignment(ren, ((jw >> 16) & 3) * 0.5, 0.5);
4085 			col = gtk_tree_view_column_new_with_attributes("", ren,
4086 				"text", j, NULL);
4087 			if (op == op_XTXTCOLUMN)
4088 				gtk_tree_view_column_set_expand(col, TRUE);
4089 		}
4090 		if (jw & 0xFFFF) g_object_set(ren, "width", jw & 0xFFFF, NULL);
4091 // !!! Maybe gtk_tree_view_column_set_fixed_width(col, jw & 0xFFFF) instead?
4092 		g_object_set(ren, "xpad", 2, NULL); // Looks good enough
4093 		gtk_tree_view_append_column(GTK_TREE_VIEW(w), col);
4094 	}
4095 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
4096 
4097 	/* This will scroll selected row in on redisplay */
4098 	g_signal_connect(w, "map", G_CALLBACK(listcc_scroll_in), NULL);
4099 
4100 	r[0] = w; // Fix up slot
4101 	if (*cnt) listcc_reset(r, -1);
4102 
4103 	g_signal_connect(w, "cursor_changed", G_CALLBACK(listcc_select), ld);
4104 
4105 	return (w);
4106 }
4107 #endif
4108 
4109 //	LISTC widget
4110 
4111 typedef struct {
4112 	int kind;		// type of list
4113 	int update;		// delayed update flags
4114 	int lock;		// against in-reset signals
4115 	int cntmax;		// maximum value from row map
4116 	int *idx;		// result field
4117 	int *cnt;		// length field
4118 	int *sort;		// sort column & direction
4119 	int **map;		// row map vector field
4120 	void **change;		// slot for EVT_CHANGE
4121 	void **ok;		// slot for EVT_OK
4122 #ifdef U_LISTS_GTK1
4123 	GtkWidget *sort_arrows[MAX_COLS];
4124 	GdkPixmap *icons[2];
4125 	GdkBitmap *masks[2];
4126 #endif
4127 	col_data c;
4128 } listc_data;
4129 
4130 #ifdef U_LISTS_GTK1
listcx_key(GtkWidget * widget,GdkEventKey * event,gpointer user_data)4131 static gboolean listcx_key(GtkWidget *widget, GdkEventKey *event,
4132 	gpointer user_data)
4133 {
4134 	listc_data *dt = user_data;
4135 	GtkCList *clist = GTK_CLIST(widget);
4136 	int row = 0;
4137 
4138 	if (dt->lock) return (FALSE); // Paranoia
4139 	switch (event->keyval)
4140 	{
4141 	case GDK_End: case GDK_KP_End:
4142 		row = clist->rows - 1;
4143 		// Fallthrough
4144 	case GDK_Home: case GDK_KP_Home:
4145 		clist->focus_row = row;
4146 		gtk_clist_select_row(clist, row, 0);
4147 		gtk_clist_moveto(clist, row, 0, 0.5, 0);
4148 		return (TRUE);
4149 	case GDK_Return: case GDK_KP_Enter:
4150 		if (!dt->ok) break;
4151 		get_evt_1(NULL, dt->ok);
4152 		return (TRUE);
4153 	}
4154 	return (FALSE);
4155 }
4156 
listcx_click(GtkWidget * widget,GdkEventButton * event,gpointer user_data)4157 static gboolean listcx_click(GtkWidget *widget, GdkEventButton *event,
4158 	gpointer user_data)
4159 {
4160 	listc_data *dt = user_data;
4161 	GtkCList *clist = GTK_CLIST(widget);
4162 	void **slot, **base, **desc;
4163 	gint row, col;
4164 
4165 	if (dt->lock) return (FALSE); // Paranoia
4166 	if ((event->button != 3) || (event->type != GDK_BUTTON_PRESS))
4167 		return (FALSE);
4168 	if (!gtk_clist_get_selection_info(clist, event->x, event->y, &row, &col))
4169 		return (FALSE);
4170 
4171 	if (clist->focus_row != row)
4172 	{
4173 		clist->focus_row = row;
4174 		gtk_clist_select_row(clist, row, 0);
4175 	}
4176 
4177 	slot = SLOT_N(dt->c.r, 2);
4178 	base = slot[0]; desc = slot[1];
4179 	if (desc[1]) ((evtx_fn)desc[1])(GET_DDATA(base), base,
4180 		(int)desc[0] & WB_OPMASK, slot, (void *)row);
4181 
4182 	return (TRUE);
4183 }
4184 
listcx_done(GtkCList * clist,listc_data * ld)4185 static void listcx_done(GtkCList *clist, listc_data *ld)
4186 {
4187 	int j;
4188 
4189 	/* Free pixmaps */
4190 	gdk_pixmap_unref(ld->icons[0]);
4191 	gdk_pixmap_unref(ld->icons[1]);
4192 	if (ld->masks[0]) gdk_pixmap_unref(ld->masks[0]);
4193 	if (ld->masks[1]) gdk_pixmap_unref(ld->masks[1]);
4194 
4195 	/* Remember column widths */
4196 	for (j = 0; j < ld->c.ncol; j++)
4197 	{
4198 		void **cp = ld->c.columns[j][1];
4199 		int op = (int)cp[0];
4200 		int l = WB_GETLEN(op); // !!! -2 for each extra ref
4201 
4202 		if (l > 4) inifile_set_gint32(cp[5], clist->column[j].width);
4203 	}
4204 }
4205 
listc_select_row(GtkCList * clist,gint row,gint column,GdkEventButton * event,gpointer user_data)4206 static void listc_select_row(GtkCList *clist, gint row, gint column,
4207 	GdkEventButton *event, gpointer user_data)
4208 {
4209 	listc_data *dt = user_data;
4210 	void **slot = NEXT_SLOT(dt->c.r), **base = slot[0], **desc = slot[1];
4211 	int dclick;
4212 
4213 	if (dt->lock) return;
4214 	dclick = dt->ok && event && (event->type == GDK_2BUTTON_PRESS);
4215 	/* Update the value */
4216 	*dt->idx = (int)gtk_clist_get_row_data(clist, row);
4217 	/* Call the handler */
4218 	if (desc[1]) ((evt_fn)desc[1])(GET_DDATA(base), base,
4219 		(int)desc[0] & WB_OPMASK, slot);
4220 	/* Call the other handler */
4221 	if (dclick) get_evt_1(NULL, dt->ok);
4222 }
4223 
listc_update(GtkWidget * w,gpointer user_data)4224 static void listc_update(GtkWidget *w, gpointer user_data)
4225 {
4226 	GtkCList *clist = GTK_CLIST(w);
4227 	listc_data *ld = user_data;
4228 	int what = ld->update;
4229 
4230 
4231 	if (!GTK_WIDGET_MAPPED(w)) /* Is frozen anyway */
4232 	{
4233 		// Flag a waiting refresh
4234 		if (what & 1) ld->update = (what & 2) | 4;
4235 		return;
4236 	}
4237 
4238 	ld->update = 0;
4239 	if (what & 4) /* Do a delayed refresh */
4240 	{
4241 		gtk_clist_freeze(clist);
4242 		gtk_clist_thaw(clist);
4243 	}
4244 	if ((what & 2) && clist->selection) /* Do a scroll */
4245 		gtk_clist_moveto(clist, (int)(clist->selection->data), 0, 0.5, 0);
4246 }
4247 
listc_collect(gchar ** row_text,gchar ** row_pix,col_data * c,int row)4248 static int listc_collect(gchar **row_text, gchar **row_pix, col_data *c, int row)
4249 {
4250 	int j, res = FALSE, ncol = c->ncol;
4251 
4252 	if (row_pix) memset(row_pix, 0, ncol * sizeof(*row_pix));
4253 	for (j = 0; j < ncol; j++)
4254 	{
4255 		char *v = get_cell(c, row, j);
4256 		void **cp = c->columns[j][1];
4257 		int op = (int)cp[0] & WB_OPMASK;
4258 
4259 // !!! IDXCOLUMN not supported
4260 		if (op == op_FILECOLUMN)
4261 		{
4262 			if (!row_pix || (v[0] == ' ')) v++;
4263 			else row_pix[j] = v , v = "" , res = TRUE;
4264 		}
4265 		row_text[j] = v;
4266 	}
4267 	return (res);
4268 }
4269 
listc_get_order(GtkCList * clist,int * res,int l)4270 static void listc_get_order(GtkCList *clist, int *res, int l)
4271 {
4272 	int i;
4273 
4274 	for (i = 0; i < l; i++) res[i] = -1; // nowhere by default
4275 	if (l > clist->rows) l = clist->rows;
4276 	for (i = 0; i < l; i++)
4277 		res[(int)gtk_clist_get_row_data(clist, i)] = i;
4278 }
4279 
listc_sort(GtkCList * clist,listc_data * ld,int reselect)4280 static int listc_sort(GtkCList *clist, listc_data *ld, int reselect)
4281 {
4282 	void **slot;
4283 
4284 	/* Do nothing if empty */
4285 	if (!*ld->cnt) return (0);
4286 
4287 	/* Call & apply external sort */
4288 	if ((slot = ld->change))
4289 	{
4290 		GList *tmp, *cur, **ll, *pos = NULL;
4291 		int i, cnt, *map;
4292 
4293 		/* Call the EVT_CHANGE handler, to sort map vector */
4294 		get_evt_1(NULL, slot);
4295 
4296 		/* !!! Directly rearrange widget's internals; it's deprecated
4297 		 * so nothing inside will change anymore */
4298 		gtk_clist_freeze(clist);
4299 
4300 		if (clist->selection) pos = g_list_nth(clist->row_list,
4301 			GPOINTER_TO_INT(clist->selection->data));
4302 
4303 		ll = calloc(ld->cntmax + 1, sizeof(*ll));
4304 		for (cur = clist->row_list; cur; cur = cur->next)
4305 		{
4306 			GtkCListRow *row = cur->data;
4307 			ll[(int)row->data] = cur;
4308 		}
4309 
4310 		/* Rearrange rows */
4311 		cnt = *ld->cnt;
4312 		map = *ld->map;
4313 		clist->row_list = cur = ll[map[0]];
4314 		cur->prev = NULL;
4315 		for (i = 1; i < cnt; i++)
4316 		{
4317 			cur->next = tmp = ll[map[i]];
4318 			tmp->prev = cur;
4319 			cur = tmp;
4320 		}
4321 		clist->row_list_end = cur;
4322 		cur->next = NULL;
4323 
4324 		free(ll);
4325 
4326 		if (pos) /* Relocate existing selection */
4327 		{
4328 			int n = g_list_position(clist->row_list, pos);
4329 			clist->selection->data = GINT_TO_POINTER(n);
4330 			clist->focus_row = n;
4331 		}
4332 
4333 	}
4334 	/* Do builtin sort */
4335 	else gtk_clist_sort(clist);
4336 
4337 	if (!reselect || !clist->selection)
4338 	{
4339 		int n = gtk_clist_find_row_from_data(clist, (gpointer)*ld->idx);
4340 		if (n < 0) *ld->idx = (int)gtk_clist_get_row_data(clist, n = 0);
4341 		clist->focus_row = n;
4342 		gtk_clist_select_row(clist, n, 0);
4343 	}
4344 
4345 	/* Done rearranging rows */
4346 	if (slot)
4347 	{
4348 		gtk_clist_thaw(clist);
4349 		return (1); // Refresh later if invisible now
4350 	}
4351 
4352 	/* !!! Builtin sort doesn't move focus along with selection, do it here */
4353 	if (clist->selection)
4354 	{
4355 		int n = GPOINTER_TO_INT(clist->selection->data);
4356 		if (clist->focus_row != n)
4357 		{
4358 			clist->focus_row = n;
4359 			if (GTK_WIDGET_HAS_FOCUS((GtkWidget *)clist))
4360 				return (4); // Refresh
4361 		}
4362 	}
4363 	return (0);
4364 }
4365 
listc_select_index(GtkWidget * widget,int v)4366 static void listc_select_index(GtkWidget *widget, int v)
4367 {
4368 	GtkCList *clist = GTK_CLIST(widget);
4369 	int row = gtk_clist_find_row_from_data(clist, (gpointer)v);
4370 
4371 	if (row < 0) return; // Paranoia
4372 	gtk_clist_select_row(clist, row, 0);
4373 	/* !!! Focus fails to follow selection in browse mode - have to
4374 	 * move it here, but a full redraw is necessary afterwards */
4375 	if (clist->focus_row == row) return;
4376 	clist->focus_row = row;
4377 	if (GTK_WIDGET_HAS_FOCUS(widget) && !clist->freeze_count)
4378 		gtk_widget_queue_draw(widget);
4379 }
4380 
listc_reset_row(GtkCList * clist,listc_data * ld,int n)4381 static void listc_reset_row(GtkCList *clist, listc_data *ld, int n)
4382 {
4383 	gchar *row_text[MAX_COLS];
4384 	int i, row, ncol = ld->c.ncol;
4385 
4386 	// !!! No support for anything but text columns
4387 	listc_collect(row_text, NULL, &ld->c, n);
4388 	row = gtk_clist_find_row_from_data(clist, (gpointer)n);
4389 	for (i = 0; i < ncol; i++) gtk_clist_set_text(clist, row, i, row_text[i]);
4390 }
4391 
4392 /* !!! Should not redraw old things while resetting - or at least, not refer
4393  * outside of new data if doing it */
listc_reset(GtkCList * clist,listc_data * ld)4394 static void listc_reset(GtkCList *clist, listc_data *ld)
4395 {
4396 	int i, j, m, n, ncol = ld->c.ncol, cnt = *ld->cnt, *map = NULL;
4397 
4398 	ld->lock = TRUE;
4399 	gtk_clist_freeze(clist);
4400 	gtk_clist_clear(clist);
4401 
4402 	if (ld->map) map = *ld->map;
4403 	for (m = i = 0; i < cnt; i++)
4404 	{
4405 		gchar *row_text[MAX_COLS], *row_pix[MAX_COLS];
4406 		int row, pix;
4407 
4408 		n = map ? map[i] : i;
4409 		if (m < n) m = n;
4410 		pix = listc_collect(row_text, row_pix, &ld->c, n);
4411 		row = gtk_clist_append(clist, row_text);
4412 		gtk_clist_set_row_data(clist, row, (gpointer)n);
4413 		if (!pix) continue;
4414 		for (j = 0; j < ncol; j++)
4415 		{
4416 			char *s = row_pix[j];
4417 			if (!s) continue;
4418 			pix = s[0] == 'D';
4419 // !!! Spacing = 4
4420 			gtk_clist_set_pixtext(clist, row, j, s + 1, 4,
4421 				ld->icons[pix], ld->masks[pix]);
4422 		}
4423 	}
4424 	ld->cntmax = m;
4425 
4426 	/* Adjust column widths (not for draggable list) */
4427 	if ((ld->kind != op_LISTCd) && (ld->kind != op_LISTCX))
4428 	{
4429 		for (j = 0; j < ncol; j++)
4430 		{
4431 // !!! Spacing = 5
4432 			gtk_clist_set_column_width(clist, j,
4433 				5 + gtk_clist_optimal_column_width(clist, j));
4434 		}
4435 	}
4436 
4437 	i = *ld->idx;
4438 	if (i >= cnt) i = cnt - 1;
4439 	if (!cnt) *ld->idx = 0;	/* Safer than -1 for empty list */
4440 	/* Draggable and unordered lists aren't sorted */
4441 	else if ((ld->kind == op_LISTCd) || (ld->kind == op_LISTCu))
4442 	{
4443 		if (i < 0) i = 0;
4444 		gtk_clist_select_row(clist, i, 0);
4445 		*ld->idx = i;
4446 	}
4447 	else
4448 	{
4449 		*ld->idx = i;
4450 		listc_sort(clist, ld, FALSE);
4451 	}
4452 
4453 	gtk_clist_thaw(clist);
4454 
4455 	/* !!! Otherwise the newly empty rows are not cleared on Windows */
4456 	gtk_widget_queue_draw((GtkWidget *)clist);
4457 
4458 	ld->update |= 3;
4459 	listc_update((GtkWidget *)clist, ld);
4460 	ld->lock = FALSE;
4461 }
4462 
listc_column_button(GtkCList * clist,gint col,gpointer user_data)4463 static void listc_column_button(GtkCList *clist, gint col, gpointer user_data)
4464 {
4465 	listc_data *dt = user_data;
4466 	int sort = *dt->sort;
4467 
4468 	if (col < 0) col = abs(sort) - 1; /* Sort as is */
4469 	else if (abs(sort) == col + 1) sort = -sort; /* Reverse same column */
4470 	else /* Select another column */
4471 	{
4472 		gtk_widget_hide(dt->sort_arrows[abs(sort) - 1]);
4473 		gtk_widget_show(dt->sort_arrows[col]);
4474 		sort = col + 1;
4475 	}
4476 	*dt->sort = sort;
4477 
4478 	gtk_clist_set_sort_column(clist, col);
4479 	gtk_clist_set_sort_type(clist, sort > 0 ? GTK_SORT_ASCENDING :
4480 		GTK_SORT_DESCENDING);
4481 	gtk_arrow_set(GTK_ARROW(dt->sort_arrows[col]), sort > 0 ?
4482 		GTK_ARROW_DOWN : GTK_ARROW_UP, GTK_SHADOW_IN);
4483 
4484 	/* Sort and maybe redraw */
4485 	dt->update |= listc_sort(clist, dt, TRUE);
4486 	/* Scroll to selected row */
4487 	dt->update |= 2;
4488 	listc_update((GtkWidget *)clist, dt);
4489 }
4490 
listc_sort_by(GtkCList * clist,listc_data * ld,int n)4491 static void listc_sort_by(GtkCList *clist, listc_data *ld, int n)
4492 {
4493 	if (!*ld->sort) return;
4494 	*ld->sort = n;
4495 	listc_column_button(clist, -1, ld);
4496 }
4497 
listc_prepare(GtkWidget * w,gpointer user_data)4498 static void listc_prepare(GtkWidget *w, gpointer user_data)
4499 {
4500 	listc_data *ld = user_data;
4501 	GtkCList *clist = GTK_CLIST(w);
4502 	int j;
4503 
4504 	if (ld->kind == op_LISTCX)
4505 	{
4506 		/* Ensure enough space for pixmaps */
4507 		gtk_clist_set_row_height(clist, 0);
4508 		if (clist->row_height < 16) gtk_clist_set_row_height(clist, 16);
4509 	}
4510 
4511 	/* Adjust width for columns which use sample text */
4512 	for (j = 0; j < ld->c.ncol; j++)
4513 	{
4514 		void **cp = ld->c.columns[j][1];
4515 		int op = (int)cp[0];
4516 		int l = WB_GETLEN(op); // !!! -2 for each extra ref
4517 
4518 		if (l < 6) continue;
4519 		l = gdk_string_width(
4520 #if GTK_MAJOR_VERSION == 1
4521 			w->style->font,
4522 #else /* if GTK_MAJOR_VERSION == 2 */
4523 			gtk_style_get_font(w->style),
4524 #endif
4525 			cp[6]);
4526 		if (clist->column[j].width < l)
4527 			gtk_clist_set_column_width(clist, j, l);
4528 	}
4529 
4530 	/* To avoid repeating after unrealize (likely won't happen anyway) */
4531 //	gtk_signal_disconnect_by_func(GTK_OBJECT(w),
4532 //		GTK_SIGNAL_FUNC(listc_prepare), user_data);
4533 }
4534 
4535 // !!! With inlining this, problem also likely
listc(void ** r,char * ddata,col_data * c)4536 GtkWidget *listc(void **r, char *ddata, col_data *c)
4537 {
4538 	static int zero = 0;
4539 	GtkWidget *list, *hbox;
4540 	GtkCList *clist;
4541 	listc_data *ld = r[2];
4542 	void **pp = r[1];
4543 	int *cntv, *sort = &zero, **map = NULL;
4544 	int j, w, sm, kind, heads = 0;
4545 
4546 
4547 	cntv = (void *)(ddata + (int)pp[2]); // length var
4548 	kind = (int)pp[0] & WB_OPMASK; // kind of list
4549 	if ((kind == op_LISTCS) || (kind == op_LISTCX))
4550 	{
4551 		sort = (void *)(ddata + (int)pp[3]); // sort mode
4552 		if (kind == op_LISTCX)
4553 			map = (void *)(ddata + (int)pp[4]); // row map
4554 	}
4555 
4556 	list = gtk_clist_new(c->ncol);
4557 
4558 	/* Fill datastruct */
4559 	ld->kind = kind;
4560 	ld->idx = r[0];
4561 	ld->cnt = cntv;
4562 	ld->sort = sort;
4563 	ld->map = map;
4564 	set_columns(&ld->c, c, ddata, r);
4565 
4566 	sm = *sort;
4567 	clist = GTK_CLIST(list);
4568 	for (j = 0; j < ld->c.ncol; j++)
4569 	{
4570 		void **cp = ld->c.columns[j][1];
4571 		int op = (int)cp[0], jw = (int)cp[3];
4572 		int l = WB_GETLEN(op); // !!! -2 for each extra ref
4573 
4574 		gtk_clist_set_column_resizeable(clist, j, kind == op_LISTCX);
4575 		if ((w = jw & 0xFFFF))
4576 		{
4577 			if (l > 4) w = inifile_get_gint32(cp[5], w);
4578 			gtk_clist_set_column_width(clist, j, w);
4579 		}
4580 		/* Left justification is default */
4581 		jw = (jw >> 16) & 3;
4582 		if (jw) gtk_clist_set_column_justification(clist, j,
4583 			jw == 1 ? GTK_JUSTIFY_CENTER : GTK_JUSTIFY_RIGHT);
4584 
4585 		hbox = gtk_hbox_new(FALSE, 0);
4586 		(!jw ? pack : jw == 1 ? xpack : pack_end)(hbox,
4587 			gtk_label_new((l > 3) && *(char *)cp[4] ? _(cp[4]) : ""));
4588 		heads += l > 3;
4589 		gtk_widget_show_all(hbox);
4590 		// !!! Must be before gtk_clist_column_title_passive()
4591 		gtk_clist_set_column_widget(clist, j, hbox);
4592 
4593 		if (sm) ld->sort_arrows[j] = pack_end(hbox,
4594 			gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_IN));
4595 		else gtk_clist_column_title_passive(clist, j);
4596 		if (kind == op_LISTCX) GTK_WIDGET_UNSET_FLAGS(
4597 			GTK_CLIST(clist)->column[j].button, GTK_CAN_FOCUS);
4598 	}
4599 
4600 	if (sm)
4601 	{
4602 		int c = abs(sm) - 1;
4603 
4604 		gtk_widget_show(ld->sort_arrows[c]);	// Show sort arrow
4605 		gtk_clist_set_sort_column(clist, c);
4606 		gtk_clist_set_sort_type(clist, sm > 0 ? GTK_SORT_ASCENDING :
4607 			GTK_SORT_DESCENDING);
4608 		gtk_arrow_set(GTK_ARROW(ld->sort_arrows[c]), sm > 0 ?
4609 			GTK_ARROW_DOWN : GTK_ARROW_UP, GTK_SHADOW_IN);
4610 		gtk_signal_connect(GTK_OBJECT(clist), "click_column",
4611 			GTK_SIGNAL_FUNC(listc_column_button), ld);
4612 	}
4613 
4614 	if (sm || heads) gtk_clist_column_titles_show(clist); // Hide if useless
4615 	gtk_clist_set_selection_mode(clist, GTK_SELECTION_BROWSE);
4616 
4617 	if (kind == op_LISTCX)
4618 	{
4619 #ifdef GTK_STOCK_DIRECTORY
4620 		ld->icons[1] = render_stock_pixmap(main_window,
4621 			GTK_STOCK_DIRECTORY, &ld->masks[1]);
4622 #endif
4623 #ifdef GTK_STOCK_FILE
4624 		ld->icons[0] = render_stock_pixmap(main_window,
4625 			GTK_STOCK_FILE, &ld->masks[0]);
4626 #endif
4627 		if (!ld->icons[1]) ld->icons[1] = gdk_pixmap_create_from_xpm_d(
4628 			main_window->window, &ld->masks[1], NULL, xpm_open_xpm);
4629 		if (!ld->icons[0]) ld->icons[0] = gdk_pixmap_create_from_xpm_d(
4630 			main_window->window, &ld->masks[0], NULL, xpm_new_xpm);
4631 
4632 		gtk_signal_connect(GTK_OBJECT(clist), "key_press_event",
4633 			GTK_SIGNAL_FUNC(listcx_key), ld);
4634 		gtk_signal_connect(GTK_OBJECT(clist), "button_press_event",
4635 			GTK_SIGNAL_FUNC(listcx_click), ld);
4636 	}
4637 
4638 	/* For some finishing touches */
4639 	gtk_signal_connect(GTK_OBJECT(clist), "realize",
4640 		GTK_SIGNAL_FUNC(listc_prepare), ld);
4641 
4642 	/* This will apply delayed updates when they can take effect */
4643 	gtk_signal_connect(GTK_OBJECT(clist), "map",
4644 		GTK_SIGNAL_FUNC(listc_update), ld);
4645 
4646 	if (*cntv) listc_reset(clist, ld);
4647 
4648 	gtk_signal_connect(GTK_OBJECT(clist), "select_row",
4649 		GTK_SIGNAL_FUNC(listc_select_row), ld);
4650 
4651 	if (kind == op_LISTCd) clist_enable_drag(list); // draggable rows
4652 
4653 	return (list);
4654 }
4655 #endif
4656 
4657 #ifndef U_LISTS_GTK1
4658 static GQuark listc_key;
4659 
4660 /* Low 8 bits is column index, others denote type */
4661 #define CELL_TEXT  0x000
4662 #define CELL_FTEXT 0x100
4663 #define CELL_ICON  0x200
4664 #define CELL_TMASK 0xF00
4665 #define CELL_XMASK 0x0FF
4666 
listcx_click(GtkWidget * widget,GdkEventButton * event,gpointer user_data)4667 static gboolean listcx_click(GtkWidget *widget, GdkEventButton *event,
4668 	gpointer user_data)
4669 {
4670 	listc_data *dt = user_data;
4671 	GtkTreeView *tree = GTK_TREE_VIEW(widget);
4672 	GtkTreePath *tp, *tp0;
4673 	void **slot, **base, **desc;
4674 	int row;
4675 
4676 	if (dt->lock) return (FALSE); // Paranoia
4677 	if ((event->button != 3) || (event->type != GDK_BUTTON_PRESS) ||
4678 		(event->window != gtk_tree_view_get_bin_window(tree)) ||
4679 		!gtk_tree_view_get_path_at_pos(tree, event->x, event->y, &tp,
4680 			NULL, NULL, NULL)) return (FALSE);
4681 
4682 	row = gtk_tree_path_get_indices(tp)[0];
4683 	gtk_tree_view_get_cursor(tree, &tp0, NULL);
4684 	/* Move cursor to where the click was */
4685 	if (!tp0 || (row != gtk_tree_path_get_indices(tp0)[0]))
4686 		gtk_tree_view_set_cursor_on_cell(tree, tp, NULL, NULL, FALSE);
4687 	gtk_tree_path_free(tp0);
4688 	gtk_tree_path_free(tp);
4689 
4690 	slot = SLOT_N(dt->c.r, 2);
4691 	base = slot[0]; desc = slot[1];
4692 	if (desc[1]) ((evtx_fn)desc[1])(GET_DDATA(base), base,
4693 		(int)desc[0] & WB_OPMASK, slot, (void *)row);
4694 
4695 	return (TRUE);
4696 }
4697 
listcx_done(GtkTreeView * tree,listc_data * ld)4698 static void listcx_done(GtkTreeView *tree, listc_data *ld)
4699 {
4700 	int j;
4701 
4702 	/* Remember column widths */
4703 	for (j = 0; j < ld->c.ncol; j++)
4704 	{
4705 		void **cp = ld->c.columns[j][1];
4706 		int op = (int)cp[0];
4707 		int l = WB_GETLEN(op); // !!! -2 for each extra ref
4708 
4709 		if (l > 4) inifile_set_gint32(cp[5], gtk_tree_view_column_get_width(
4710 			gtk_tree_view_get_column(tree, j)));
4711 	}
4712 }
4713 
listcx_act(GtkTreeView * tree,GtkTreePath * tp,GtkTreeViewColumn * col,gpointer user_data)4714 static void listcx_act(GtkTreeView *tree, GtkTreePath *tp,
4715 	GtkTreeViewColumn *col, gpointer user_data)
4716 {
4717 	listc_data *dt = user_data;
4718 
4719 	if (dt->lock) return; // Paranoia
4720 	if (dt->ok) get_evt_1(NULL, dt->ok);
4721 }
4722 
listc_select_row(GtkTreeView * tree,gpointer user_data)4723 static void listc_select_row(GtkTreeView *tree, gpointer user_data)
4724 {
4725 	listc_data *dt = user_data;
4726 	void **slot = NEXT_SLOT(dt->c.r), **base = slot[0], **desc = slot[1];
4727 	GtkTreeModel *tm;
4728 	GtkTreePath *tp;
4729 	GtkTreeIter it;
4730 	gint row;
4731 
4732 	if (dt->lock) return;
4733 	/* Update the value */
4734 	gtk_tree_view_get_cursor(tree, &tp, NULL);
4735 	tm = gtk_tree_view_get_model(tree);
4736 	row = tp && gtk_tree_model_get_iter(tm, &it, tp);
4737 	gtk_tree_path_free(tp);
4738 	if (!row) return; // Paranoia
4739 	gtk_tree_model_get(tm, &it, 0, &row, -1);
4740 	*dt->idx = row;
4741 
4742 	/* Call the handler */
4743 	if (desc[1]) ((evt_fn)desc[1])(GET_DDATA(base), base,
4744 		(int)desc[0] & WB_OPMASK, slot);
4745 }
4746 
listc_select_item(GtkTreeView * tree,int idx)4747 static void listc_select_item(GtkTreeView *tree, int idx)
4748 {
4749 	GtkTreePath *tp = gtk_tree_path_new_from_indices(idx, -1);
4750 	gtk_tree_view_set_cursor_on_cell(tree, tp, NULL, NULL, FALSE);
4751 	gtk_tree_view_scroll_to_cell(tree, tp, NULL, TRUE, 0.5, 0.0);
4752 	gtk_tree_path_free(tp);
4753 }
4754 
4755 /* If no such index, return row 0 with its index */
listc_find_index(GtkTreeModel * tm,int idx,int * whatwhere)4756 static void listc_find_index(GtkTreeModel *tm, int idx, int *whatwhere)
4757 {
4758 	GtkTreeIter it;
4759 	int n = 0;
4760 	gint pos;
4761 
4762 	whatwhere[0] = whatwhere[1] = 0;
4763 	if (!gtk_tree_model_get_iter_first(tm, &it)) return;
4764 	gtk_tree_model_get(tm, &it, 0, &pos, -1);
4765 	whatwhere[0] = pos;
4766 	if (idx < 0) return;
4767 	while (pos != idx)
4768 	{
4769 		if (!gtk_tree_model_iter_next(tm, &it)) return;
4770 		gtk_tree_model_get(tm, &it, 0, &pos, -1);
4771 		n++;
4772 	}
4773 	/* Found! */
4774 	whatwhere[0] = idx;
4775 	whatwhere[1] = n;
4776 }
4777 
listc_select_index(GtkTreeView * tree,int idx)4778 static void listc_select_index(GtkTreeView *tree, int idx)
4779 {
4780 	GtkTreeModel *tm = gtk_tree_view_get_model(tree);
4781 	int whatwhere[2];
4782 
4783 	listc_find_index(tm, idx, whatwhere);
4784 	listc_select_item(tree, whatwhere[1]);
4785 }
4786 
listc_get_order(GtkTreeView * tree,int * res,int l)4787 static void listc_get_order(GtkTreeView *tree, int *res, int l)
4788 {
4789 	GtkTreeModel *tm = gtk_tree_view_get_model(tree);
4790 	GtkTreeIter it;
4791 	int i, n = gtk_tree_model_iter_n_children(tm, NULL);
4792 	gint pos;
4793 
4794 	for (i = 0; i < l; i++) res[i] = -1; // nowhere by default
4795 	if (l > n) l = n;
4796 	if (!gtk_tree_model_get_iter_first(tm, &it)) return;
4797 	for (i = 0; i < l; i++)
4798 	{
4799 		gtk_tree_model_get(tm, &it, 0, &pos, -1);
4800 		res[pos] = i;
4801 		if (!gtk_tree_model_iter_next(tm, &it)) return;
4802 	}
4803 }
4804 
listc_sort_func(GtkTreeModel * tm,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)4805 static gint listc_sort_func(GtkTreeModel *tm, GtkTreeIter *a, GtkTreeIter *b,
4806 	gpointer user_data)
4807 {
4808 	listc_data *ld = user_data;
4809 	char *v0, *v1;
4810 	int n, dir, cell, sort = *ld->sort;
4811 	gint row0, row1;
4812 
4813 	sort += !sort; // Default is column 0 in ascending order
4814 	dir = sort > 0 ? 1 : -1;
4815 	cell = sort * dir - 1;
4816 	gtk_tree_model_get(tm, a, 0, &row0, -1);
4817 	gtk_tree_model_get(tm, b, 0, &row1, -1);
4818 	v0 = get_cell(&ld->c, row0, cell);
4819 	v1 = get_cell(&ld->c, row1, cell);
4820 	n = strcmp(v0 ? v0 : "", v1 ? v1 : ""); // Better safe than sorry
4821 	if (!n) n = row0 - row1;
4822 	return (n * dir);
4823 }
4824 
listc_sort(GtkTreeView * tree,listc_data * ld,int reselect)4825 static int listc_sort(GtkTreeView *tree, listc_data *ld, int reselect)
4826 {
4827 	GtkTreeModel *tm = gtk_tree_view_get_model(tree);
4828 	GtkListStore *ls;
4829 	GtkTreePath *tp;
4830 	GtkTreeIter it;
4831 	void **slot;
4832 	int whatwhere[2];
4833 	int i, have_pos, cnt = *ld->cnt, *map = NULL;
4834 	gint pos = 0;
4835 
4836 	/* Do nothing if empty */
4837 	if (!cnt) return (0);
4838 
4839 	ls = GTK_LIST_STORE(tm);
4840 	/* Get current position if any */
4841 	gtk_tree_view_get_cursor(tree, &tp, NULL);
4842 	if ((have_pos = tp && gtk_tree_model_get_iter(tm, &it, tp)))
4843 		gtk_tree_model_get(tm, &it, 0, &pos, -1);
4844 	gtk_tree_path_free(tp);
4845 
4846 	/* Call & apply external sort */
4847 	if ((slot = ld->change))
4848 	{
4849 		/* Call the EVT_CHANGE handler, to sort map vector */
4850 		get_evt_1(NULL, slot);
4851 
4852 		/* Rearrange rows */ // !!! On unfreezed widget
4853 		cnt = *ld->cnt;
4854 		map = *ld->map;
4855 		gtk_tree_model_get_iter_first(tm, &it);
4856 		for (i = 0; i < cnt; i++)
4857 		{
4858 			gtk_list_store_set(ls, &it, 0, map[i], -1);
4859 			gtk_tree_model_iter_next(tm, &it);
4860 		}
4861 	}
4862 	/* Do builtin sort */
4863 	else
4864 	{
4865 		// Tell it it's unsorted, first
4866 		gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(tm),
4867 			GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
4868 		// Then set to sorted again; real sort order is in *ld->sort
4869 		gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(tm),
4870 			1, GTK_SORT_ASCENDING);
4871 	}
4872 
4873 	/* Relocate existing position (visual update) */
4874 	if (reselect && have_pos)
4875 	{
4876 		listc_find_index(tm, pos, whatwhere);
4877 		ld->lock++;
4878 		listc_select_item(tree, whatwhere[1]);
4879 		ld->lock--;
4880 	}
4881 	/* Set position anew */
4882 	else
4883 	{
4884 		listc_find_index(tm, *ld->idx, whatwhere);
4885 		*ld->idx = whatwhere[0];
4886 		listc_select_item(tree, whatwhere[1]);
4887 	}
4888 	/* Done rearranging rows */
4889 	return (!!slot);
4890 }
4891 
4892 /* !!! This will brutally recalculate the entire list; may be slow if large */
listc_optimal_width(GtkTreeView * tree,listc_data * ld)4893 static void listc_optimal_width(GtkTreeView *tree, listc_data *ld)
4894 {
4895 	GtkTreeModel *tm = gtk_tree_view_get_model(tree);
4896 	int i, j;
4897 #if GTK_MAJOR_VERSION == 3
4898 	/* !!! GTK+2 accounts for vertical separators, and resizes columns to fit
4899 	 * header buttons; GTK+3 does neither */
4900 	int hvis = gtk_tree_view_get_headers_visible(tree);
4901 	gint vsep;
4902 	gtk_widget_style_get(GTK_WIDGET(tree), "vertical-separator", &vsep, NULL);
4903 #endif
4904 
4905 	for (j = 0; j < ld->c.ncol; j++)
4906 	{
4907 		GtkTreeViewColumn *col = gtk_tree_view_get_column(tree, j);
4908 		GtkTreeIter it;
4909 		gint width;
4910 
4911 		gtk_tree_view_column_queue_resize(col); // Reset width
4912 		if (!gtk_tree_model_get_iter_first(tm, &it)) continue;
4913 		for (i = 0; i < 500; i++) // Stop early if overlarge
4914 		{
4915 			gtk_tree_view_column_cell_set_cell_data(col, tm, &it,
4916 				FALSE, FALSE);
4917 			gtk_tree_view_column_cell_get_size(col, NULL, NULL, NULL,
4918 				&width, NULL); // It returns max of all
4919 			if (!gtk_tree_model_iter_next(tm, &it)) break;
4920 		}
4921 #if GTK_MAJOR_VERSION == 3
4922 		if (hvis)
4923 		{
4924 			GtkWidget *button = gtk_tree_view_column_get_button(col);
4925 			if (button)
4926 			{
4927 				gint bw;
4928 				gtk_widget_get_preferred_width(button, &bw, NULL);
4929 				if (width < bw) width = bw;
4930 			}
4931 		}
4932 		width += vsep;
4933 #endif
4934 		gtk_tree_view_column_set_fixed_width(col, width);
4935 	}
4936 }
4937 
4938 /* GtkTreeView here displays everything by reference, refill not needed */
4939 #define listc_reset_row(A,B,C) gtk_widget_queue_draw(A)
4940 
4941 /* !!! Should not redraw old things while resetting - or at least, not refer
4942  * outside of new data if doing it */
listc_reset(GtkTreeView * tree,listc_data * ld)4943 static void listc_reset(GtkTreeView *tree, listc_data *ld)
4944 {
4945 	GtkTreeModel *tm;
4946 	int i, cnt = *ld->cnt, sort = *ld->sort;
4947 
4948 
4949 	ld->lock = TRUE;
4950 	tm = gtk_tree_view_get_model(tree);
4951 
4952 	/* Rebuild the index vector, unless it surely stays the same */
4953 	if (!tm || (gtk_tree_model_iter_n_children(tm, NULL) != cnt) ||
4954 		(ld->kind == op_LISTCd) || ld->map)
4955 	{
4956 		GtkListStore *ls = gtk_list_store_new(1, G_TYPE_INT);
4957 		GtkTreeIter it;
4958 		int m, n, *map = ld->map ? *ld->map : NULL;
4959 
4960 		for (m = i = 0; i < cnt; i++)
4961 		{
4962 			n = map ? map[i] : i;
4963 			if (m < n) m = n;
4964 			gtk_list_store_append(ls, &it);
4965 			gtk_list_store_set(ls, &it, 0, n, -1);
4966 		}
4967 		tm = GTK_TREE_MODEL(ls);
4968 		ld->cntmax = m;
4969 
4970 		/* Let it sit there in case it's needed */
4971 		gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ls), 1,
4972 			listc_sort_func, ld, NULL);
4973 	}
4974 	gtk_tree_view_set_model(tree, tm);
4975 
4976 	/* !!! Sort arrow gets lost after resetting model */
4977 	if (sort)
4978 	{
4979 		GtkTreeViewColumn *col = gtk_tree_view_get_column(tree, abs(sort) - 1);
4980 		gtk_tree_view_column_set_sort_indicator(col, TRUE); // Show sort arrow
4981 	}
4982 
4983 	/* Adjust column widths (not for draggable list) */
4984 	if ((ld->kind != op_LISTCd) && (ld->kind != op_LISTCX))
4985 	{
4986 		if (gtk_widget_get_mapped(GTK_WIDGET(tree)))
4987 			listc_optimal_width(tree, ld);
4988 		else ld->update |= 1; // Do it later
4989 	}
4990 
4991 	i = *ld->idx;
4992 	if (i >= cnt) i = cnt - 1;
4993 	if (!cnt) *ld->idx = 0;	/* Safer than -1 for empty list */
4994 	/* Draggable and unordered lists aren't sorted */
4995 	else if ((ld->kind == op_LISTCd) || (ld->kind == op_LISTCu))
4996 	{
4997 		if (i < 0) i = 0;
4998 		listc_select_item(tree, i);
4999 		*ld->idx = i;
5000 	}
5001 	else
5002 	{
5003 		*ld->idx = i;
5004 		listc_sort(tree, ld, FALSE);
5005 	}
5006 
5007 	/* !!! Sometimes it shows the wrong part and redraw doesn't help */
5008 	gtk_adjustment_value_changed(gtk_tree_view_get_vadjustment(tree));
5009 
5010 	ld->lock = FALSE;
5011 }
5012 
listc_getcell(GtkTreeViewColumn * col,GtkCellRenderer * ren,GtkTreeModel * tm,GtkTreeIter * it,gpointer data)5013 void listc_getcell(GtkTreeViewColumn *col, GtkCellRenderer *ren, GtkTreeModel *tm,
5014 	GtkTreeIter *it, gpointer data)
5015 {
5016 	listc_data *dt = g_object_get_qdata(G_OBJECT(ren), listc_key);
5017 	char *s;
5018 	int cell = (int)data;
5019 	gint row;
5020 
5021 	gtk_tree_model_get(tm, it, 0, &row, -1);
5022 	s = get_cell(&dt->c, row, cell & CELL_XMASK);
5023 	if (!s) s = "\0"; // NULL is used for empty cells in some places
5024 	cell &= CELL_TMASK;
5025 	/* TEXT/FTEXT */
5026 	if (cell != CELL_ICON)
5027 		g_object_set(ren, "text", s + (cell == CELL_FTEXT), NULL);
5028 	/* ICON */
5029 	else g_object_set(ren, "visible", s[0] != ' ',
5030 #if GTK_MAJOR_VERSION == 2
5031 		"stock-id", (s[0] == 'D' ? GTK_STOCK_DIRECTORY : GTK_STOCK_FILE), NULL);
5032 #else
5033 		"icon-name", (s[0] == 'D' ? "folder" : "text-x-generic"), NULL);
5034 #endif
5035 }
5036 
5037 #if GTK_MAJOR_VERSION == 2
5038 #define gtk_tree_view_column_get_button(A) ((A)->button)
5039 #endif
5040 
5041 /* Use of sort arrows should NOT cause sudden redirect of keyboard input */
listc_defocus(GObject * obj,GParamSpec * pspec,gpointer user_data)5042 static void listc_defocus(GObject *obj, GParamSpec *pspec, gpointer user_data)
5043 {
5044 	GtkWidget *button = gtk_tree_view_column_get_button(GTK_TREE_VIEW_COLUMN(obj));
5045 	if (!button || !gtk_widget_get_can_focus(button)) return;
5046 	gtk_widget_set_can_focus(button, FALSE);
5047 }
5048 
listc_column_button(GtkTreeViewColumn * col,gpointer user_data)5049 static void listc_column_button(GtkTreeViewColumn *col, gpointer user_data)
5050 {
5051 	listc_data *dt = g_object_get_qdata(G_OBJECT(col), listc_key);
5052 	GtkTreeView *tree = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(col));
5053 	int sort = *dt->sort, idx = (int)user_data;
5054 
5055 	if (idx > CELL_XMASK); /* Sort as is */
5056 	else if (abs(sort) == idx + 1) sort = -sort; /* Reverse same column */
5057 	else /* Select another column */
5058 	{
5059 		GtkTreeViewColumn *col0 = gtk_tree_view_get_column(tree, abs(sort) - 1);
5060 		gtk_tree_view_column_set_sort_indicator(col0, FALSE);
5061 		gtk_tree_view_column_set_sort_indicator(col, TRUE);
5062 		sort = idx + 1;
5063 	}
5064 	*dt->sort = sort;
5065 
5066 	gtk_tree_view_column_set_sort_order(col, sort > 0 ?
5067 			GTK_SORT_ASCENDING : GTK_SORT_DESCENDING);
5068 
5069 	/* Sort and maybe redraw */
5070 	listc_sort(tree, dt, TRUE);
5071 }
5072 
listc_sort_by(GtkTreeView * tree,listc_data * ld,int n)5073 static void listc_sort_by(GtkTreeView *tree, listc_data *ld, int n)
5074 {
5075 	GtkTreeViewColumn *col;
5076 
5077 	if (!*ld->sort) return;
5078 	col = gtk_tree_view_get_column(tree, abs(*ld->sort) - 1);
5079 	*ld->sort = n;
5080 	listc_column_button(col, (gpointer)CELL_XMASK + 1); // Impossible index as flag
5081 }
5082 
listc_update(GtkWidget * widget,gpointer user_data)5083 static void listc_update(GtkWidget *widget, gpointer user_data)
5084 {
5085 	listc_data *ld = user_data;
5086 	int what = ld->update;
5087 
5088 	ld->update = 0;
5089 	if (what & 1) listc_optimal_width(GTK_TREE_VIEW(widget), ld);
5090 }
5091 
listc_prepare(GtkWidget * widget,gpointer user_data)5092 static void listc_prepare(GtkWidget *widget, gpointer user_data)
5093 {
5094 	listc_data *ld = user_data;
5095 	GtkTreeView *tree = GTK_TREE_VIEW(widget);
5096 	PangoContext *context = gtk_widget_create_pango_context(widget);
5097 	PangoLayout *layout = pango_layout_new(context);
5098 	int j;
5099 
5100 	for (j = 0; j < ld->c.ncol; j++)
5101 	{
5102 		void **cp = ld->c.columns[j][1];
5103 		int op = (int)cp[0];
5104 		int l = WB_GETLEN(op); // !!! -2 for each extra ref
5105 		GtkTreeViewColumn *col = gtk_tree_view_get_column(tree, j);
5106 		GtkWidget *button = gtk_tree_view_column_get_button(col);
5107 		gint width, lw;
5108 
5109 		/* Prevent sort buttons' narrowing down */
5110 		if (ld->kind == op_LISTCS) widget_set_keepsize(button, FALSE);
5111 		/* Adjust width for columns which use sample text */
5112 		if (l < 6) continue;
5113 		pango_layout_set_text(layout, cp[6], -1);
5114 		pango_layout_get_pixel_size(layout, &lw, NULL);
5115 		gtk_tree_view_column_cell_get_size(col, NULL, NULL, NULL, &width, NULL);
5116 		if (width < lw) gtk_tree_view_column_set_fixed_width(col, lw);
5117 	}
5118 	g_object_unref(layout);
5119 	g_object_unref(context);
5120 
5121 	/* To avoid repeating after unrealize (likely won't happen anyway) */
5122 //	g_signal_handlers_disconnect_by_func(widget, listc_prepare, user_data);
5123 }
5124 
5125 #define LISTC_XPAD 2
5126 
listc(void ** r,char * ddata,col_data * c)5127 GtkWidget *listc(void **r, char *ddata, col_data *c)
5128 {
5129 	static int zero = 0;
5130 	GtkWidget *w;
5131 	GtkTreeView *tree;
5132 	listc_data *ld = r[2];
5133 	void **pp = r[1];
5134 	int *cntv, *sort = &zero, **map = NULL;
5135 	int j, sm, kind, heads = 0;
5136 
5137 
5138 	listc_key = g_quark_from_static_string(LISTCC_KEY);
5139 
5140 	cntv = (void *)(ddata + (int)pp[2]); // length var
5141 	kind = (int)pp[0] & WB_OPMASK; // kind of list
5142 	if ((kind == op_LISTCS) || (kind == op_LISTCX))
5143 	{
5144 		sort = (void *)(ddata + (int)pp[3]); // sort mode
5145 		if (kind == op_LISTCX)
5146 			map = (void *)(ddata + (int)pp[4]); // row map
5147 	}
5148 
5149 	w = gtk_tree_view_new();
5150 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(w), FALSE);
5151 	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(w)),
5152 		GTK_SELECTION_BROWSE);
5153 
5154 	/* Fill datastruct */
5155 	ld->kind = kind;
5156 	ld->idx = r[0];
5157 	ld->cnt = cntv;
5158 	ld->sort = sort;
5159 	ld->map = map;
5160 	set_columns(&ld->c, c, ddata, r);
5161 
5162 	sm = *sort;
5163 	tree = GTK_TREE_VIEW(w);
5164 	for (j = 0; j < ld->c.ncol; j++)
5165 	{
5166 		GtkCellRenderer *ren;
5167 		GtkTreeViewColumn *col = gtk_tree_view_column_new();
5168 		void **cp = ld->c.columns[j][1];
5169 		int op = (int)cp[0], jw = (int)cp[3];
5170 		int width, l = WB_GETLEN(op); // !!! -2 for each extra ref
5171 
5172 		op &= WB_OPMASK;
5173 		gtk_tree_view_column_set_resizable(col, kind == op_LISTCX);
5174 		if (kind == op_LISTCX) gtk_tree_view_column_set_sizing(col,
5175 			GTK_TREE_VIEW_COLUMN_FIXED);
5176 		if ((width = jw & 0xFFFF))
5177 		{
5178 			if (l > 4) width = inifile_get_gint32(cp[5], width);
5179 			gtk_tree_view_column_set_fixed_width(col, width);
5180 		}
5181 		/* Left/center/right justification */
5182 		jw = (jw >> 16) & 3;
5183 		gtk_tree_view_column_set_alignment(col, jw * 0.5);
5184 		gtk_tree_view_column_set_title(col, (l > 3) && *(char *)cp[4] ?
5185 			_(cp[4]) : "");
5186 		heads += l > 3;
5187 		gtk_tree_view_column_set_expand(col, op == op_XTXTCOLUMN);
5188 		if (sm)
5189 		{
5190 			g_signal_connect(col, "notify", G_CALLBACK(listc_defocus), NULL);
5191 			g_signal_connect(col, "clicked",
5192 				G_CALLBACK(listc_column_button), (gpointer)j);
5193 			g_object_set_qdata(G_OBJECT(col), listc_key, ld);
5194 		}
5195 		if (op == op_FILECOLUMN)
5196 		{
5197 			ren = gtk_cell_renderer_pixbuf_new();
5198 			g_object_set(ren, "stock-size", GTK_ICON_SIZE_SMALL_TOOLBAR,
5199 				"xpad", LISTC_XPAD, NULL);
5200 //			g_object_set(col, "spacing", 2); // !!! If ever needed
5201 			gtk_tree_view_column_pack_start(col, ren, FALSE);
5202 			gtk_tree_view_column_set_cell_data_func(col, ren, listc_getcell,
5203 				(gpointer)(CELL_ICON + j), NULL);
5204 			g_object_set_qdata(G_OBJECT(ren), listc_key, ld);
5205 		}
5206 		ren = gtk_cell_renderer_text_new();
5207 		gtk_cell_renderer_set_alignment(ren, jw * 0.5, 0.5);
5208 		g_object_set(ren, "xpad", LISTC_XPAD, NULL);
5209 		gtk_tree_view_column_pack_start(col, ren, TRUE);
5210 		gtk_tree_view_column_set_cell_data_func(col, ren, listc_getcell,
5211 			(gpointer)((op == op_FILECOLUMN ? CELL_FTEXT : CELL_TEXT) + j), NULL);
5212 		g_object_set_qdata(G_OBJECT(ren), listc_key, ld);
5213 
5214 		gtk_tree_view_append_column(GTK_TREE_VIEW(w), col);
5215 	}
5216 
5217 	if (sm)
5218 	{
5219 		GtkTreeViewColumn *col = gtk_tree_view_get_column(tree, abs(sm) - 1);
5220 		gtk_tree_view_column_set_sort_indicator(col, TRUE); // Show sort arrow
5221 		gtk_tree_view_column_set_sort_order(col, sm > 0 ?
5222 			GTK_SORT_ASCENDING : GTK_SORT_DESCENDING);
5223 		gtk_tree_view_set_headers_clickable(tree, TRUE);
5224 	}
5225 
5226 	gtk_tree_view_set_headers_visible(tree, sm || heads); // Hide if useless
5227 
5228 	if (kind == op_LISTCX)
5229 	{
5230 		g_signal_connect(w, "button_press_event", G_CALLBACK(listcx_click), ld);
5231 		g_signal_connect(w, "row-activated", G_CALLBACK(listcx_act), ld);
5232 	}
5233 
5234 	/* For some finishing touches */
5235 	g_signal_connect(w, "realize", G_CALLBACK(listc_prepare), ld);
5236 	/* This will apply delayed updates when they can take effect */
5237 	g_signal_connect(w, "map", G_CALLBACK(listc_update), ld);
5238 
5239 	if (*cntv) listc_reset(tree, ld);
5240 
5241 	g_signal_connect(w, "cursor_changed", G_CALLBACK(listc_select_row), ld);
5242 
5243 	gtk_tree_view_set_reorderable(tree, kind == op_LISTCd); // draggable rows
5244 
5245 	gtk_widget_show(w); /* !!! Need this for sizing-on-realize */
5246 
5247 	return (w);
5248 }
5249 #endif
5250 
5251 //	uLISTC widget
5252 
5253 typedef struct {
5254 	swdata s;
5255 	col_data c;
5256 } lswdata;
5257 
ulistc_reset(void ** wdata)5258 static int ulistc_reset(void **wdata)
5259 {
5260 	lswdata *ld = wdata[2];
5261 	int l = *(int *)(ld->c.ddata + (int)GET_DESCV(wdata, 2));
5262 	int idx = *(int *)ld->s.strs;
5263 
5264 	/* Constrain the index */
5265 	if (idx >= l) idx = l - 1;
5266 	if (idx < 0) idx = 0;
5267 	*(int *)ld->s.strs = idx;
5268 
5269 	return (idx);
5270 }
5271 
5272 //	PATH widget
5273 
pathbox_button(GtkWidget * widget,gpointer user_data)5274 static void pathbox_button(GtkWidget *widget, gpointer user_data)
5275 {
5276 	void **slot = user_data, **desc = slot[1];
5277 	void *xdata[2] = { _(desc[2]), slot }; // title and slot
5278 
5279 	file_selector_x((int)desc[3], xdata);
5280 }
5281 
5282 // !!! With inlining this, problem also
pathbox(void ** r,int border)5283 GtkWidget *pathbox(void **r, int border)
5284 {
5285 	GtkWidget *hbox, *entry, *button;
5286 
5287 	hbox = hbox_new(5 - 2);
5288 	gtk_widget_show(hbox);
5289 	gtk_container_set_border_width(GTK_CONTAINER(hbox), border);
5290 
5291 	entry = xpack(hbox, gtk_entry_new());
5292 	button = pack(hbox, gtk_button_new_with_label(_("Browse")));
5293 	gtk_container_set_border_width(GTK_CONTAINER(button), 2);
5294 	gtk_widget_show(button);
5295 
5296 	gtk_signal_connect(GTK_OBJECT(button), "clicked",
5297 		GTK_SIGNAL_FUNC(pathbox_button), r);
5298 
5299 	return (entry);
5300 }
5301 
5302 /* Set value of path widget */
set_path(GtkWidget * widget,char * s,int mode)5303 static void set_path(GtkWidget *widget, char *s, int mode)
5304 {
5305 	char path[PATHTXT];
5306 	if (mode == PATH_VALUE) s = gtkuncpy(path, s, PATHTXT);
5307 	gtk_entry_set_text(GTK_ENTRY(widget), s);
5308 }
5309 
5310 //	SPINPACK widget
5311 
5312 #define TLSPINPACK_SIZE(P) (((int)(P)[2] * 2 + 1) * sizeof(void **))
5313 
spinpack_evt(GtkAdjustment * adj,gpointer user_data)5314 static void spinpack_evt(GtkAdjustment *adj, gpointer user_data)
5315 {
5316 	void **tp = user_data, **vp = *tp, **r = *vp;
5317 	void **slot, **base, **desc;
5318 	char *ddata;
5319 	int *v, idx = (tp - vp) / 2 - 1;
5320 
5321 	if (!r) return; // Lock
5322 	/* Locate the data */
5323 	slot = NEXT_SLOT(r);
5324 	base = slot[0];
5325 	ddata = GET_DDATA(base);
5326 	/* Locate the cell in array */
5327 	desc = r[1];
5328 	v = desc[1];
5329 	if ((int)desc[0] & WB_FFLAG) v = (void *)(ddata + (int)v);
5330 	v += idx * 3;
5331 	/* Read the value */
5332 	*v = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(*(tp - 1)));
5333 	/* Call the handler */
5334 	desc = slot[1];
5335 	if (desc[1]) ((evtx_fn)desc[1])(ddata, base, (int)desc[0] & WB_OPMASK,
5336 		slot, (void *)idx);
5337 }
5338 
5339 // !!! With inlining this, problem also
tlspinpack(void ** r,void ** vp,GtkWidget * table,int wh)5340 GtkWidget *tlspinpack(void **r, void **vp, GtkWidget *table, int wh)
5341 {
5342 	GtkWidget *widget = NULL;
5343 	void **tp = vp, **pp = r[1];
5344 	int row, column, i, l, n, *np = r[0];
5345 
5346 	n = (int)pp[2];
5347 	row = wh & 255;
5348 	column = (wh >> 8) & 255;
5349 	l = (wh >> 16) + 1;
5350 
5351 	*tp++ = r;
5352 
5353 	for (i = 0; i < n; i++ , np += 3)
5354 	{
5355 		int x = i % l, y = i / l;
5356 		*tp++ = widget = add_a_spin(np[0], np[1], np[2]);
5357 		/* Value might get clamped, and slot is self-reading so should
5358 		 * reflect that */
5359 		np[0] = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
5360 		spin_connect(widget, GTK_SIGNAL_FUNC(spinpack_evt), tp);
5361 // !!! Spacing = 2
5362 		gtk_table_attach(GTK_TABLE(table), widget,
5363 			column + x, column + x + 1, row + y, row + y + 1,
5364 			GTK_EXPAND | GTK_FILL, 0, 0, 2);
5365 		*tp++ = vp;
5366 	}
5367 	return (widget);
5368 }
5369 
5370 //	TLTEXT widget
5371 
5372 // !!! Even with inlining this, some space gets wasted
tltext(char * v,void ** pp,GtkWidget * table,int pad)5373 void tltext(char *v, void **pp, GtkWidget *table, int pad)
5374 {
5375 	GtkWidget *label;
5376 	char *tmp, *s;
5377 	int x, wh, row, column;
5378 
5379 	tmp = s = strdup(v);
5380 
5381 	wh = (int)pp[2];
5382 	row = wh & 255;
5383 	x = column = (wh >> 8) & 255;
5384 
5385 	while (TRUE)
5386 	{
5387 		int i = strcspn(tmp, "\t\n");
5388 		int c = tmp[i];
5389 		tmp[i] = '\0';
5390 		label = gtk_label_new(tmp);
5391 		gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
5392 		gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
5393 		gtk_widget_show(label);
5394 		gtk_table_attach(GTK_TABLE(table), label, x, x + 1, row, row + 1,
5395 			GTK_FILL, 0, pad, pad);
5396 		x++;
5397 		if (!c) break;
5398 		if (c == '\n') x = column , row++;
5399 		tmp += i + 1;
5400 	}
5401 	free(s);
5402 }
5403 
5404 //	TOOLBAR widget
5405 
5406 /* !!! These pass the button slot, not event slot, as event source */
5407 
toolbar_lclick(GtkWidget * widget,gpointer user_data)5408 static void toolbar_lclick(GtkWidget *widget, gpointer user_data)
5409 {
5410 	void **slot = user_data;
5411 	void **base = slot[0], **desc = slot[1];
5412 
5413 	/* Ignore radio buttons getting depressed */
5414 #if GTK_MAJOR_VERSION == 3
5415 	if (GTK_IS_RADIO_TOOL_BUTTON(widget) && !gtk_toggle_tool_button_get_active(
5416 		GTK_TOGGLE_TOOL_BUTTON(widget))) return;
5417 	slot = g_object_get_qdata(G_OBJECT(widget), tool_key); // if initialized
5418 #else /* #if GTK_MAJOR_VERSION <= 2 */
5419 	if (GTK_IS_RADIO_BUTTON(widget) && !GTK_TOGGLE_BUTTON(widget)->active)
5420 		return;
5421 	slot = gtk_object_get_user_data(GTK_OBJECT(widget)); // if initialized
5422 #endif
5423 	if (slot) ((evt_fn)desc[1])(GET_DDATA(base), base,
5424 		(int)desc[0] & WB_OPMASK, slot);
5425 }
5426 
toolbar_rclick(GtkWidget * widget,GdkEventButton * event,gpointer user_data)5427 static gboolean toolbar_rclick(GtkWidget *widget, GdkEventButton *event,
5428 	gpointer user_data)
5429 {
5430 	void **slot = user_data;
5431 	void **base = slot[0], **desc = slot[1];
5432 
5433 	/* Handle only right clicks */
5434 	if ((event->type != GDK_BUTTON_PRESS) || (event->button != 3))
5435 		return (FALSE);
5436 #if GTK_MAJOR_VERSION == 3
5437 	slot = g_object_get_qdata(G_OBJECT(widget), tool_key); // if initialized
5438 #else /* #if GTK_MAJOR_VERSION <= 2 */
5439 	slot = gtk_object_get_user_data(GTK_OBJECT(widget)); // if initialized
5440 #endif
5441 	if (slot) ((evt_fn)desc[1])(GET_DDATA(base), base,
5442 		(int)desc[0] & WB_OPMASK, slot);
5443 	return (TRUE);
5444 }
5445 
5446 //	SMARTTBAR widget
5447 
5448 #if GTK_MAJOR_VERSION == 3
5449 
5450 /* This one handler, for keeping overflow menu untouchable */
leave_be(GtkToolItem * tool_item,gpointer user_data)5451 static gboolean leave_be(GtkToolItem *tool_item, gpointer user_data)
5452 {
5453 	return (TRUE);
5454 }
5455 
5456 #else /* #if GTK_MAJOR_VERSION <= 2 */
5457 
5458 /* The following is main toolbars auto-sizing code. If toolbar is too long for
5459  * the window, some of its items get hidden, but remain accessible through an
5460  * "overflow box" - a popup with 5xN grid of buttons inside. This way, we can
5461  * support small-screen devices without penalizing large-screen ones. - WJ */
5462 
5463 typedef struct {
5464 	void **r, **r2;	// slot and end-of-items slot
5465 	GtkWidget *button, *tbar, *vport, *popup, *bbox;
5466 } smarttbar_data;
5467 
5468 #define WRAPBOX_W 5
5469 
wrapbox_size_req(GtkWidget * widget,GtkRequisition * req,gpointer user_data)5470 static void wrapbox_size_req(GtkWidget *widget, GtkRequisition *req,
5471 	gpointer user_data)
5472 {
5473 	GtkBox *box = GTK_BOX(widget);
5474 	GtkBoxChild *child;
5475 	GList *chain;
5476 	GtkRequisition wreq;
5477 	int cnt, nr, w, h, l, spacing;
5478 
5479 	cnt = w = h = spacing = 0;
5480 	for (chain = box->children; chain; chain = chain->next)
5481 	{
5482 		child = chain->data;
5483 		if (!GTK_WIDGET_VISIBLE(child->widget)) continue;
5484 		gtk_widget_size_request(child->widget, &wreq);
5485 		if (w < wreq.width) w = wreq.width;
5486 		if (h < wreq.height) h = wreq.height;
5487 		cnt++;
5488 	}
5489 	if (cnt) spacing = box->spacing;
5490 	nr = (cnt + WRAPBOX_W - 1) / WRAPBOX_W;
5491 	cnt = nr > 1 ? WRAPBOX_W : cnt;
5492 
5493 	l = GTK_CONTAINER(widget)->border_width * 2 - spacing;
5494 	req->width = (w + spacing) * cnt + l;
5495 	req->height = (h + spacing) * nr + l;
5496 }
5497 
wrapbox_size_alloc(GtkWidget * widget,GtkAllocation * alloc,gpointer user_data)5498 static void wrapbox_size_alloc(GtkWidget *widget, GtkAllocation *alloc,
5499 	gpointer user_data)
5500 {
5501 	GtkBox *box = GTK_BOX(widget);
5502 	GtkBoxChild *child;
5503 	GList *chain;
5504 	GtkRequisition wreq;
5505 	GtkAllocation wall;
5506 	int idx, cnt, nr, l, w, h, ww, wh, spacing;
5507 
5508 	widget->allocation = *alloc;
5509 
5510 	/* Count widgets */
5511 	cnt = w = h = 0;
5512 	for (chain = box->children; chain; chain = chain->next)
5513 	{
5514 		child = chain->data;
5515 		if (!GTK_WIDGET_VISIBLE(child->widget)) continue;
5516 		gtk_widget_get_child_requisition(child->widget, &wreq);
5517 		if (w < wreq.width) w = wreq.width;
5518 		if (h < wreq.height) h = wreq.height;
5519 		cnt++;
5520 	}
5521 	if (!cnt) return; // Nothing needs positioning in here
5522 	nr = (cnt + WRAPBOX_W - 1) / WRAPBOX_W;
5523 	cnt = nr > 1 ? WRAPBOX_W : cnt;
5524 
5525 	/* Adjust sizes (homogeneous, shrinkable, no expand, no fill) */
5526 	l = GTK_CONTAINER(widget)->border_width;
5527 	spacing = box->spacing;
5528 	ww = alloc->width - l * 2 + spacing;
5529 	wh = alloc->height - l * 2 + spacing;
5530 	if ((w + spacing) * cnt > ww) w = ww / cnt - spacing;
5531 	if (w < 1) w = 1;
5532 	if ((h + spacing) * nr > wh) h = wh / nr - spacing;
5533 	if (h < 1) h = 1;
5534 
5535 	/* Now position the widgets */
5536 	wall.height = h;
5537 	wall.width = w;
5538 	idx = 0;
5539 	for (chain = box->children; chain; chain = chain->next)
5540 	{
5541 		child = chain->data;
5542 		if (!GTK_WIDGET_VISIBLE(child->widget)) continue;
5543 		wall.x = alloc->x + l + (w + spacing) * (idx % WRAPBOX_W);
5544 		wall.y = alloc->y + l + (h + spacing) * (idx / WRAPBOX_W);
5545 		gtk_widget_size_allocate(child->widget, &wall);
5546 		idx++;
5547 	}
5548 }
5549 
split_toolbar_at(GtkWidget * tbar,int w)5550 static int split_toolbar_at(GtkWidget *tbar, int w)
5551 {
5552 	GList *chain;
5553 	GtkToolbarChild *child;
5554 	GtkAllocation *alloc;
5555 	int border, x = 0;
5556 
5557 	if (w < 1) w = 1;
5558 	if (!tbar) return (w);
5559 	border = GTK_CONTAINER(tbar)->border_width;
5560 	for (chain = GTK_TOOLBAR(tbar)->children; chain; chain = chain->next)
5561 	{
5562 		child = chain->data;
5563 		if (child->type == GTK_TOOLBAR_CHILD_SPACE) continue;
5564 		if (!GTK_WIDGET_VISIBLE(child->widget)) continue;
5565 		alloc = &child->widget->allocation;
5566 		if (alloc->x < w)
5567 		{
5568 			if (alloc->x + alloc->width <= w)
5569 			{
5570 				x = alloc->x + alloc->width;
5571 				continue;
5572 			}
5573 			w = alloc->x;
5574 		}
5575 		if (!x) return (1); // Nothing to see here
5576 		return (x + border > w ? x : x + border);
5577 	}
5578 	return (w); // Toolbar is empty
5579 }
5580 
htoolbox_size_req(GtkWidget * widget,GtkRequisition * req,gpointer user_data)5581 static void htoolbox_size_req(GtkWidget *widget, GtkRequisition *req,
5582 	gpointer user_data)
5583 {
5584 	smarttbar_data *sd = user_data;
5585 	GtkBox *box = GTK_BOX(widget);
5586 	GtkBoxChild *child;
5587 	GList *chain;
5588 	GtkRequisition wreq;
5589 	int cnt, w, h, l;
5590 
5591 	cnt = w = h = 0;
5592 	for (chain = box->children; chain; chain = chain->next)
5593 	{
5594 		child = chain->data;
5595 		if (!GTK_WIDGET_VISIBLE(child->widget)) continue;
5596 		gtk_widget_size_request(child->widget, &wreq);
5597 		if (h < wreq.height) h = wreq.height;
5598 		/* Button adds no extra width */
5599 		if (child->widget == sd->button) continue;
5600 		w += wreq.width + child->padding * 2;
5601 		cnt++;
5602 	}
5603 	if (cnt > 1) w += (cnt - 1) * box->spacing;
5604 	l = GTK_CONTAINER(widget)->border_width * 2;
5605 	req->width = w + l;
5606 	req->height = h + l;
5607 }
5608 
htoolbox_size_alloc(GtkWidget * widget,GtkAllocation * alloc,gpointer user_data)5609 static void htoolbox_size_alloc(GtkWidget *widget, GtkAllocation *alloc,
5610 	gpointer user_data)
5611 {
5612 	smarttbar_data *sd = user_data;
5613 	GtkBox *box = GTK_BOX(widget);
5614 	GtkBoxChild *child;
5615 	GList *chain;
5616 	GtkRequisition wreq;
5617 	GtkAllocation wall;
5618 	int vw, bw, xw, dw, pad, spacing;
5619 	int cnt, l, x, wrkw;
5620 
5621 	widget->allocation = *alloc;
5622 
5623 	/* Calculate required size */
5624 	cnt = 0;
5625 	vw = bw = xw = 0;
5626 	spacing = box->spacing;
5627 	for (chain = box->children; chain; chain = chain->next)
5628 	{
5629 		GtkWidget *w;
5630 
5631 		child = chain->data;
5632 		pad = child->padding * 2;
5633 		w = child->widget;
5634 		if (w == sd->button)
5635 		{
5636 			gtk_widget_size_request(w, &wreq);
5637 			bw = wreq.width + pad + spacing; // Button
5638 		}
5639 		else if (GTK_WIDGET_VISIBLE(w))
5640 		{
5641 			gtk_widget_get_child_requisition(w, &wreq);
5642 			if (w == sd->vport) vw = wreq.width; // Viewport
5643 			else xw += wreq.width; // Extra widgets
5644 			xw += pad;
5645 			cnt++;
5646 		}
5647 	}
5648 	if (cnt > 1) xw += (cnt - 1) * spacing;
5649 	cnt -= !!vw; // Now this counts visible extra widgets
5650 	l = GTK_CONTAINER(widget)->border_width;
5651 	xw += l * 2;
5652 	if (vw && (xw + vw > alloc->width)) /* If viewport doesn't fit */
5653 		vw = split_toolbar_at(sd->tbar, alloc->width - xw - bw);
5654 	else bw = 0;
5655 
5656 	/* Calculate how much to reduce extra widgets' sizes */
5657 	dw = 0;
5658 	if (cnt) dw = (xw + bw + vw - alloc->width + cnt - 1) / cnt;
5659 	if (dw < 0) dw = 0;
5660 
5661 	/* Now position the widgets */
5662 	x = alloc->x + l;
5663 	wall.y = alloc->y + l;
5664 	wall.height = alloc->height - l * 2;
5665 	if (wall.height < 1) wall.height = 1;
5666 	for (chain = box->children; chain; chain = chain->next)
5667 	{
5668 		GtkWidget *w;
5669 
5670 		child = chain->data;
5671 		pad = child->padding;
5672 		w = child->widget;
5673 		/* Button uses size, the others, visibility */
5674 		if (w == sd->button ? !bw : !GTK_WIDGET_VISIBLE(w)) continue;
5675 		gtk_widget_get_child_requisition(w, &wreq);
5676 		wrkw = w == sd->vport ? vw : w == sd->button ? wreq.width :
5677 			wreq.width - dw;
5678 		if (wrkw < 1) wrkw = 1;
5679 		wall.width = wrkw;
5680 		x = (wall.x = x + pad) + wrkw + pad + spacing;
5681 		gtk_widget_size_allocate(w, &wall);
5682 	}
5683 
5684 	if (sd->button) widget_showhide(sd->button, bw);
5685 }
5686 
htoolbox_popup(GtkWidget * button,gpointer user_data)5687 static void htoolbox_popup(GtkWidget *button, gpointer user_data)
5688 {
5689 	smarttbar_data *sd = user_data;
5690 	GtkWidget *popup = sd->popup;
5691 	GtkAllocation *alloc = &button->allocation;
5692 	GtkRequisition req;
5693 	GtkBox *box;
5694 	GtkBoxChild *child;
5695 	GList *chain;
5696 	gint x, y, w, h, vl;
5697 
5698 	/* Pre-grab; use an already visible widget */
5699 	if (!do_grab(GRAB_PROGRAM, button, NULL)) return;
5700 
5701 	/* Position the popup */
5702 #if GTK2VERSION >= 2 /* GTK+ 2.2+ */
5703 	{
5704 		GdkScreen *screen = gtk_widget_get_screen(button);
5705 		w = gdk_screen_get_width(screen);
5706 		h = gdk_screen_get_height(screen);
5707 		/* !!! To have styles while unrealized, need at least this */
5708 		gtk_window_set_screen(GTK_WINDOW(popup), screen);
5709 	}
5710 #else
5711 	w = gdk_screen_width();
5712 	h = gdk_screen_height();
5713 #endif
5714 	vl = sd->vport->allocation.width;
5715 	box = GTK_BOX(sd->bbox);
5716 	for (chain = box->children; chain; chain = chain->next)
5717 	{
5718 		GtkWidget *btn, *tool;
5719 		void **slot;
5720 
5721 		child = chain->data;
5722 		btn = child->widget;
5723 		slot = gtk_object_get_user_data(GTK_OBJECT(btn));
5724 		if (!slot) continue; // Paranoia
5725 		tool = slot[0];
5726 		/* Copy button relief setting of toolbar buttons */
5727 		gtk_button_set_relief(GTK_BUTTON(btn),
5728 			gtk_button_get_relief(GTK_BUTTON(tool)));
5729 		/* Copy their sensitivity */
5730 		gtk_widget_set_sensitive(GTK_WIDGET(btn),
5731 			GTK_WIDGET_SENSITIVE(GTK_WIDGET(tool)));
5732 		/* Copy their state (feedback is disabled while invisible) */
5733 		if (GTK_IS_TOGGLE_BUTTON(btn)) gtk_toggle_button_set_active(
5734 			GTK_TOGGLE_BUTTON(btn), GTK_TOGGLE_BUTTON(tool)->active);
5735 //		gtk_widget_set_style(btn, gtk_rc_get_style(tool));
5736 		/* Set visibility */
5737 		widget_showhide(btn, GTK_WIDGET_VISIBLE(tool) &&
5738 			(tool->allocation.x >= vl));
5739 	}
5740 	gtk_widget_size_request(popup, &req);
5741 	gdk_window_get_origin(GTK_WIDGET(sd->r[0])->window, &x, &y);
5742 	x += alloc->x + (alloc->width - req.width) / 2;
5743 	y += alloc->y + alloc->height;
5744 	if (x + req.width > w) x = w - req.width;
5745 	if (x < 0) x = 0;
5746 	if (y + req.height > h) y -= alloc->height + req.height;
5747 	if (y + req.height > h) y = h - req.height;
5748 	if (y < 0) y = 0;
5749 #if GTK_MAJOR_VERSION == 1
5750 	gtk_widget_realize(popup);
5751 	gtk_window_reposition(GTK_WINDOW(popup), x, y);
5752 #else /* #if GTK_MAJOR_VERSION == 2 */
5753 	gtk_window_move(GTK_WINDOW(popup), x, y);
5754 #endif
5755 
5756 	/* Actually popup it */
5757 	gtk_widget_show(popup);
5758 	gtk_window_set_focus(GTK_WINDOW(popup), NULL); // Nothing is focused
5759 	gdk_flush(); // !!! To accept grabs, window must be actually mapped
5760 
5761 	/* Transfer grab to it */
5762 	do_grab(GRAB_WIDGET, popup, NULL);
5763 }
5764 
htoolbox_popdown(GtkWidget * widget)5765 static void htoolbox_popdown(GtkWidget *widget)
5766 {
5767 	undo_grab(widget);
5768 	gtk_widget_hide(widget);
5769 }
5770 
htoolbox_unrealize(GtkWidget * widget,gpointer user_data)5771 static void htoolbox_unrealize(GtkWidget *widget, gpointer user_data)
5772 {
5773 	GtkWidget *popup = user_data;
5774 
5775 	if (GTK_WIDGET_VISIBLE(popup)) htoolbox_popdown(popup);
5776 	gtk_widget_unrealize(popup);
5777 }
5778 
htoolbox_popup_key(GtkWidget * widget,GdkEventKey * event,gpointer user_data)5779 static gboolean htoolbox_popup_key(GtkWidget *widget, GdkEventKey *event,
5780 	gpointer user_data)
5781 {
5782 	if ((event->keyval != GDK_Escape) || (event->state & (GDK_CONTROL_MASK |
5783 		GDK_SHIFT_MASK | GDK_MOD1_MASK))) return (FALSE);
5784 	htoolbox_popdown(widget);
5785 	return (TRUE);
5786 }
5787 
htoolbox_popup_click(GtkWidget * widget,GdkEventButton * event,gpointer user_data)5788 static gboolean htoolbox_popup_click(GtkWidget *widget, GdkEventButton *event,
5789 	gpointer user_data)
5790 {
5791 	GtkWidget *ev = gtk_get_event_widget((GdkEvent *)event);
5792 
5793 	/* Clicks on popup's descendants are OK; otherwise, remove the popup */
5794 	if (ev != widget)
5795 	{
5796 		while (ev)
5797 		{
5798 			ev = ev->parent;
5799 			if (ev == widget) return (FALSE);
5800 		}
5801 	}
5802 	htoolbox_popdown(widget);
5803 	return (TRUE);
5804 }
5805 
htoolbox_tool_clicked(GtkWidget * button,gpointer user_data)5806 static void htoolbox_tool_clicked(GtkWidget *button, gpointer user_data)
5807 {
5808 	smarttbar_data *sd = user_data;
5809 	void **slot;
5810 
5811 	/* Invisible buttons don't send (virtual) clicks to toolbar */
5812 	if (!GTK_WIDGET_VISIBLE(sd->popup)) return;
5813 	/* Ignore radio buttons getting depressed */
5814 	if (GTK_IS_RADIO_BUTTON(button) && !GTK_TOGGLE_BUTTON(button)->active)
5815 		return;
5816 	htoolbox_popdown(sd->popup);
5817 	slot = gtk_object_get_user_data(GTK_OBJECT(button));
5818 	gtk_button_clicked(GTK_BUTTON(slot[0]));
5819 }
5820 
htoolbox_tool_rclick(GtkWidget * widget,GdkEventButton * event,gpointer user_data)5821 static gboolean htoolbox_tool_rclick(GtkWidget *widget, GdkEventButton *event,
5822 	gpointer user_data)
5823 {
5824 	smarttbar_data *sd = user_data;
5825 	void **slot;
5826 
5827 	/* Handle only right clicks */
5828 	if ((event->type != GDK_BUTTON_PRESS) || (event->button != 3))
5829 		return (FALSE);
5830 	htoolbox_popdown(sd->popup);
5831 	slot = gtk_object_get_user_data(GTK_OBJECT(widget));
5832 	return (toolbar_rclick(slot[0], event, SLOT_N(sd->r, 2)));
5833 }
5834 
5835 // !!! With inlining this, problem also
smarttbar_button(smarttbar_data * sd,char * v)5836 GtkWidget *smarttbar_button(smarttbar_data *sd, char *v)
5837 {
5838 	GtkWidget *box = sd->r[0], *ritem = NULL;
5839 	GtkWidget *button, *arrow, *popup, *ebox, *frame, *bbox, *item;
5840 	void **slot, *rvar = MEM_NONE;
5841 
5842 	sd->button = button = pack(box, gtk_button_new());
5843 #if GTK_MAJOR_VERSION == 1
5844 	// !!! Arrow w/o shadow is invisible in plain GTK+1
5845 	arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
5846 #else /* #if GTK_MAJOR_VERSION == 2 */
5847 	arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
5848 #endif
5849 	gtk_widget_show(arrow);
5850 	gtk_container_add(GTK_CONTAINER(button), arrow);
5851 #if GTK2VERSION >= 4 /* GTK+ 2.4+ */
5852 	gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
5853 #endif
5854 	if (v) gtk_tooltips_set_tip(GTK_TOOLBAR(sd->tbar)->tooltips, button,
5855 		_(v), "Private");
5856 
5857 	sd->popup = popup = gtk_window_new(GTK_WINDOW_POPUP);
5858 	gtk_window_set_policy(GTK_WINDOW(popup), FALSE, FALSE, TRUE);
5859 #if GTK2VERSION >= 10 /* GTK+ 2.10+ */
5860 	gtk_window_set_type_hint(GTK_WINDOW(popup), GDK_WINDOW_TYPE_HINT_COMBO);
5861 #endif
5862 	gtk_signal_connect(GTK_OBJECT(button), "clicked",
5863 		GTK_SIGNAL_FUNC(htoolbox_popup), sd);
5864 	gtk_signal_connect(GTK_OBJECT(box), "unrealize",
5865 		GTK_SIGNAL_FUNC(htoolbox_unrealize), popup);
5866 	gtk_signal_connect_object(GTK_OBJECT(box), "destroy",
5867 		GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(popup));
5868 	/* Eventbox covers the popup, and popup has a grab; then, all clicks
5869 	 * inside the popup get its descendant as event widget; anything else,
5870 	 * including popup window itself, means click was outside, and triggers
5871 	 * popdown (solution from GtkCombo) - WJ */
5872 	ebox = gtk_event_box_new();
5873 	gtk_container_add(GTK_CONTAINER(popup), ebox);
5874 	frame = gtk_frame_new(NULL);
5875 	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
5876 	gtk_container_add(GTK_CONTAINER(ebox), frame);
5877 
5878 	sd->bbox = bbox = wj_size_box();
5879 	gtk_signal_connect(GTK_OBJECT(bbox), "size_request",
5880 		GTK_SIGNAL_FUNC(wrapbox_size_req), NULL);
5881 	gtk_signal_connect(GTK_OBJECT(bbox), "size_allocate",
5882 		GTK_SIGNAL_FUNC(wrapbox_size_alloc), NULL);
5883 	gtk_container_add(GTK_CONTAINER(frame), bbox);
5884 
5885 	gtk_widget_show_all(ebox);
5886 	gtk_signal_connect(GTK_OBJECT(popup), "key_press_event",
5887 		GTK_SIGNAL_FUNC(htoolbox_popup_key), NULL);
5888 	gtk_signal_connect(GTK_OBJECT(popup), "button_press_event",
5889 		GTK_SIGNAL_FUNC(htoolbox_popup_click), NULL);
5890 
5891 	for (slot = sd->r; slot - sd->r2 < 0; slot = NEXT_SLOT(slot))
5892 	{
5893 		void **desc = slot[1];
5894 		int l, op = (int)desc[0];
5895 
5896 		l = WB_GETLEN(op);
5897 		op &= WB_OPMASK;
5898 		if (op == op_TBBUTTON) item = gtk_button_new();
5899 		else if (op == op_TBTOGGLE) item = gtk_toggle_button_new();
5900 		else if (op == op_TBRBUTTON)
5901 		{
5902 			ritem = item = gtk_radio_button_new_from_widget(
5903 				rvar != desc[1] ? NULL : GTK_RADIO_BUTTON(ritem));
5904 			rvar = desc[1];
5905 			/* !!! Flags are ignored; can XOR desc[0]'s to compare
5906 			 * them too, but false match improbable anyway - WJ */
5907 			gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(item), FALSE);
5908 		}
5909 		else continue; // Not a regular toolbar button
5910 #if GTK2VERSION >= 4 /* GTK+ 2.4+ */
5911 		gtk_button_set_focus_on_click(GTK_BUTTON(item), FALSE);
5912 #endif
5913 		gtk_container_add(GTK_CONTAINER(item), xpm_image(desc[4]));
5914 		pack(bbox, item);
5915 		gtk_tooltips_set_tip(GTK_TOOLBAR(sd->tbar)->tooltips, item,
5916 			_(desc[3]), "Private");
5917 		gtk_object_set_user_data(GTK_OBJECT(item), slot);
5918 		gtk_signal_connect(GTK_OBJECT(item), "clicked",
5919 			GTK_SIGNAL_FUNC(htoolbox_tool_clicked), sd);
5920 		if (l > 4) gtk_signal_connect(GTK_OBJECT(item), "button_press_event",
5921 			GTK_SIGNAL_FUNC(htoolbox_tool_rclick), sd);
5922 	}
5923 
5924 	return (button);
5925 }
5926 
5927 //	TWOBOX widget
5928 
5929 #define TWOBAR_KEY "mtPaint.twobar"
5930 
twobar_size_req(GtkWidget * widget,GtkRequisition * req,gpointer user_data)5931 static void twobar_size_req(GtkWidget *widget, GtkRequisition *req,
5932 	gpointer user_data)
5933 {
5934 	GtkBox *box = GTK_BOX(widget);
5935 	GtkBoxChild *child;
5936 	GtkRequisition wreq1, wreq2;
5937 	int l;
5938 
5939 
5940 	wreq1.width = wreq1.height = 0;
5941 	wreq2 = wreq1;
5942 	if (box->children)
5943 	{
5944 		child = box->children->data;
5945 		if (GTK_WIDGET_VISIBLE(child->widget))
5946 			gtk_widget_size_request(child->widget, &wreq1);
5947 		if (box->children->next)
5948 		{
5949 			child = box->children->next->data;
5950 			if (GTK_WIDGET_VISIBLE(child->widget))
5951 				gtk_widget_size_request(child->widget, &wreq2);
5952 		}
5953 	}
5954 
5955 	l = box->spacing;
5956 	/* One or none */
5957 	if (!wreq2.width);
5958 	else if (!wreq1.width) wreq1 = wreq2;
5959 	/* Two in one row */
5960 	else if (gtk_object_get_data(GTK_OBJECT(widget), TWOBAR_KEY))
5961 	{
5962 		wreq1.width += wreq2.width + l;
5963 		if (wreq1.height < wreq2.height) wreq1.height = wreq2.height;
5964 	}
5965 	/* Two rows (default) */
5966 	else
5967 	{
5968 		wreq1.height += wreq2.height + l;
5969 		if (wreq1.width < wreq2.width) wreq1.width = wreq2.width;
5970 	}
5971 	/* !!! Children' padding is ignored (it isn't used anyway) */
5972 
5973 	l = GTK_CONTAINER(widget)->border_width * 2;
5974 
5975 #if GTK_MAJOR_VERSION == 1
5976 	/* !!! GTK+1 doesn't want to reallocate upper-level containers when
5977 	 * something on lower level gets downsized */
5978 	if (widget->requisition.height > wreq1.height + l) force_resize(widget);
5979 #endif
5980 
5981 	req->width = wreq1.width + l;
5982 	req->height = wreq1.height + l;
5983 }
5984 
twobar_size_alloc(GtkWidget * widget,GtkAllocation * alloc,gpointer user_data)5985 static void twobar_size_alloc(GtkWidget *widget, GtkAllocation *alloc,
5986 	gpointer user_data)
5987 {
5988 	GtkBox *box = GTK_BOX(widget);
5989 	GtkBoxChild *child, *child2 = NULL;
5990 	GtkRequisition wreq1, wreq2;
5991 	GtkAllocation wall;
5992 	int l, h, w2, ww, wh, bar, oldbar;
5993 
5994 
5995 	widget->allocation = *alloc;
5996 
5997 	if (!box->children) return; // Empty
5998 	child = box->children->data;
5999 	if (box->children->next)
6000 	{
6001 		child2 = box->children->next->data;
6002 		if (!GTK_WIDGET_VISIBLE(child2->widget)) child2 = NULL;
6003 	}
6004 	if (!GTK_WIDGET_VISIBLE(child->widget)) child = child2 , child2 = NULL;
6005 	if (!child) return;
6006 
6007 	l = GTK_CONTAINER(widget)->border_width;
6008 	wall.x = alloc->x + l;
6009 	wall.y = alloc->y + l;
6010 	l *= 2;
6011 	ww = alloc->width - l;
6012 	if (ww < 1) ww = 1;
6013 	wall.width = ww;
6014 	wh = alloc->height - l;
6015 	if (wh < 1) wh = 1;
6016 	wall.height = wh;
6017 
6018 	if (!child2) /* Place one, and be done */
6019 	{
6020 		gtk_widget_size_allocate(child->widget, &wall);
6021 		return;
6022 	}
6023 
6024 	/* Need to arrange two */
6025 	gtk_widget_get_child_requisition(child->widget, &wreq1);
6026 	gtk_widget_get_child_requisition(child2->widget, &wreq2);
6027 	l = box->spacing;
6028 	w2 = wreq1.width + wreq2.width + l;
6029 	h = wreq1.height;
6030 	if (h < wreq2.height) h = wreq2.height;
6031 
6032 	bar = w2 <= ww; /* Can do one row */
6033 	if (bar)
6034 	{
6035 		if (wall.height > h) wall.height = h;
6036 		l += (wall.width = wreq1.width);
6037 		gtk_widget_size_allocate(child->widget, &wall);
6038 		wall.x += l;
6039 		wall.width = ww - l;
6040 	}
6041 	else /* Two rows */
6042 	{
6043 		l += (wall.height = wreq1.height);
6044 		gtk_widget_size_allocate(child->widget, &wall);
6045 		wall.y += l;
6046 		wall.height = wh - l;
6047 		if (wall.height < 1) wall.height = 1;
6048 	}
6049 	gtk_widget_size_allocate(child2->widget, &wall);
6050 
6051 	oldbar = (int)gtk_object_get_data(GTK_OBJECT(widget), TWOBAR_KEY);
6052 	if (bar != oldbar) /* Shape change */
6053 	{
6054 		gtk_object_set_data(GTK_OBJECT(widget), TWOBAR_KEY, (gpointer)bar);
6055 		/* !!! GTK+1 doesn't handle requeued resizes properly */
6056 #if GTK_MAJOR_VERSION == 1
6057 		force_resize(widget);
6058 #else
6059 		gtk_widget_queue_resize(widget);
6060 #endif
6061 	}
6062 }
6063 
6064 #endif /* GTK+1&2 */
6065 
6066 //	MENUBAR widget
6067 
6068 /* !!! This passes the item slot, not event slot, as event source */
6069 
menu_evt(GtkWidget * widget,gpointer user_data)6070 static void menu_evt(GtkWidget *widget, gpointer user_data)
6071 {
6072 	void **slot = user_data;
6073 	void **base = slot[0], **desc = slot[1];
6074 
6075 	/* Ignore radio buttons getting depressed */
6076 #if GTK_MAJOR_VERSION == 3
6077 	if (GTK_IS_RADIO_MENU_ITEM(widget) && !gtk_check_menu_item_get_active(
6078 		GTK_CHECK_MENU_ITEM(widget))) return;
6079 	slot = g_object_get_qdata(G_OBJECT(widget), tool_key); // if initialized
6080 #else /* #if GTK_MAJOR_VERSION <= 2 */
6081 	if (GTK_IS_RADIO_MENU_ITEM(widget) && !GTK_CHECK_MENU_ITEM(widget)->active)
6082 		return;
6083 	slot = gtk_object_get_user_data(GTK_OBJECT(widget));
6084 #endif
6085 	((evt_fn)desc[1])(GET_DDATA(base), base, (int)desc[0] & WB_OPMASK, slot);
6086 }
6087 
6088 #if (GTK_MAJOR_VERSION == 3) || (GTK2VERSION >= 4) /* Not needed before GTK+ 2.4 */
6089 
6090 /* Ignore shortcut key only when item itself is insensitive or hidden */
menu_allow_key(GtkWidget * widget,guint signal_id,gpointer user_data)6091 static gboolean menu_allow_key(GtkWidget *widget, guint signal_id, gpointer user_data)
6092 {
6093 	return (GTK_WIDGET_IS_SENSITIVE(widget) && GTK_WIDGET_VISIBLE(widget));
6094 }
6095 
6096 #endif
6097 
6098 //	SMARTMENU widget
6099 
6100 /* The following is main menu auto-rearrange code. If the menu is too long for
6101  * the window, some of its items are moved into "overflow" submenu - and moved
6102  * back to menubar when the window is made wider. This way, we can support
6103  * small-screen devices without penalizing large-screen ones. - WJ */
6104 
6105 #define MENU_RESIZE_MAX 16
6106 
6107 typedef struct {
6108 	void **slot;
6109 	GtkWidget *fallback;
6110 	guint key;
6111 	int width;
6112 } r_menu_slot;
6113 
6114 typedef struct {
6115 	void **r; // own slot
6116 	GtkWidget *mbar;
6117 	int r_menu_state;
6118 	int in_alloc;
6119 	r_menu_slot r_menu[MENU_RESIZE_MAX];
6120 } smartmenu_data;
6121 
6122 /* Handle keyboard accels for overflow menu */
check_smart_menu_keys(void * sdata,GdkEventKey * event)6123 static int check_smart_menu_keys(void *sdata, GdkEventKey *event)
6124 {
6125 	smartmenu_data *sd = sdata;
6126 	r_menu_slot *slot;
6127 	guint lowkey;
6128 	int l = sd->r_menu_state;
6129 
6130 	/* No overflow - nothing to do */
6131 	if (!l) return (FALSE);
6132 	/* Menu hidden - do nothing */
6133 	if (!GTK_WIDGET_VISIBLE(sd->r[0])) return (FALSE);
6134 	/* Alt+key only */
6135 	if ((event->state & _CSAmask) != _Amask) return (FALSE);
6136 
6137 	lowkey = low_key(event);
6138 	for (slot = sd->r_menu + 1; slot->key != lowkey; slot++)
6139 		if (--l <= 0) return (FALSE); // No such key in overflow
6140 
6141 	/* Just popup - if we're here, overflow menu is offscreen anyway */
6142 	gtk_menu_popup(GTK_MENU(gtk_menu_item_get_submenu(GTK_MENU_ITEM(slot->fallback))),
6143 		NULL, NULL, NULL, NULL, 0, 0);
6144 	return (TRUE);
6145 }
6146 
6147 #if GTK_MAJOR_VERSION <= 2 /* !!! FOR NOW */
6148 
6149 /* Invalidate width cache after width-affecting change */
check_width_cache(smartmenu_data * sd,int width)6150 static void check_width_cache(smartmenu_data *sd, int width)
6151 {
6152 	r_menu_slot *slot, *sm = sd->r_menu + sd->r_menu_state;
6153 
6154 	if (sm->width == width) return;
6155 	if (sm->width) for (slot = sd->r_menu; slot->slot; slot++)
6156 		slot->width = 0;
6157 	sm->width = width;
6158 }
6159 
6160 /* Show/hide widgets according to new state */
change_to_state(smartmenu_data * sd,int state)6161 static void change_to_state(smartmenu_data *sd, int state)
6162 {
6163 	GtkWidget *w;
6164 	r_menu_slot *r_menu = sd->r_menu;
6165 	int i, oldst = sd->r_menu_state;
6166 
6167 	if (oldst < state)
6168 	{
6169 		for (i = oldst + 1; i <= state; i++)
6170 			gtk_widget_hide(r_menu[i].slot[0]);
6171 		if (oldst == 0)
6172 		{
6173 			w = r_menu[0].slot[0];
6174 			gtk_widget_set_state(w, GTK_STATE_NORMAL);
6175 			gtk_widget_show(w);
6176 		}
6177 	}
6178 	else
6179 	{
6180 		for (i = oldst; i > state; i--)
6181 		{
6182 			w = r_menu[i].slot[0];
6183 			gtk_widget_set_state(w, GTK_STATE_NORMAL);
6184 			gtk_widget_show(w);
6185 		}
6186 		if (state == 0) gtk_widget_hide(r_menu[0].slot[0]);
6187 	}
6188 	sd->r_menu_state = state;
6189 }
6190 
6191 /* Move submenus between menubar and overflow submenu */
switch_states(smartmenu_data * sd,int newstate,int oldstate)6192 static void switch_states(smartmenu_data *sd, int newstate, int oldstate)
6193 {
6194 	r_menu_slot *r_menu = sd->r_menu;
6195 	GtkWidget *submenu;
6196 	GtkMenuItem *item;
6197 	int i;
6198 
6199 	if (newstate < oldstate) /* To main menu */
6200 	{
6201 		for (i = oldstate; i > newstate; i--)
6202 		{
6203 			gtk_widget_hide(r_menu[i].fallback);
6204 			item = GTK_MENU_ITEM(r_menu[i].fallback);
6205 			gtk_widget_ref(submenu = item->submenu);
6206 			gtk_menu_item_remove_submenu(item);
6207 			item = GTK_MENU_ITEM(r_menu[i].slot[0]);
6208 			gtk_menu_item_set_submenu(item, submenu);
6209 			gtk_widget_unref(submenu);
6210 		}
6211 	}
6212 	else /* To overflow submenu */
6213 	{
6214 		for (i = oldstate + 1; i <= newstate; i++)
6215 		{
6216 			item = GTK_MENU_ITEM(r_menu[i].slot[0]);
6217 			gtk_widget_ref(submenu = item->submenu);
6218 			gtk_menu_item_remove_submenu(item);
6219 			item = GTK_MENU_ITEM(r_menu[i].fallback);
6220 			gtk_menu_item_set_submenu(item, submenu);
6221 			gtk_widget_unref(submenu);
6222 			gtk_widget_show(r_menu[i].fallback);
6223 		}
6224 	}
6225 }
6226 
6227 /* Get width request for default state */
smart_menu_full_width(smartmenu_data * sd,GtkWidget * widget,int width)6228 static int smart_menu_full_width(smartmenu_data *sd, GtkWidget *widget, int width)
6229 {
6230 	check_width_cache(sd, width);
6231 	if (!sd->r_menu[0].width)
6232 	{
6233 		GtkRequisition req;
6234 		GtkWidget *child = sd->mbar; /* aka BOX_CHILD_0(widget) */
6235 		int oldst = sd->r_menu_state;
6236 		gpointer lock = toggle_updates(widget, NULL);
6237 		change_to_state(sd, 0);
6238 		gtk_widget_size_request(child, &req);
6239 		sd->r_menu[0].width = req.width;
6240 		change_to_state(sd, oldst);
6241 		child->requisition.width = width;
6242 		toggle_updates(widget, lock);
6243 	}
6244 	return (sd->r_menu[0].width);
6245 }
6246 
6247 /* Switch to the state which best fits the allocated width */
smart_menu_state_to_width(smartmenu_data * sd,GtkWidget * widget,int rwidth,int awidth)6248 static void smart_menu_state_to_width(smartmenu_data *sd, GtkWidget *widget,
6249 	int rwidth, int awidth)
6250 {
6251 	r_menu_slot *slot;
6252 	GtkWidget *child = BOX_CHILD_0(widget);
6253 	gpointer lock = NULL;
6254 	int state, oldst, newst;
6255 
6256 	check_width_cache(sd, rwidth);
6257 	state = oldst = sd->r_menu_state;
6258 	while (TRUE)
6259 	{
6260 		newst = rwidth < awidth ? state - 1 : state + 1;
6261 		slot = sd->r_menu + newst;
6262 		if ((newst < 0) || !slot->slot) break;
6263 		if (!slot->width)
6264 		{
6265 			GtkRequisition req;
6266 			if (!lock) lock = toggle_updates(widget, NULL);
6267 			change_to_state(sd, newst);
6268 			gtk_widget_size_request(child, &req);
6269 			slot->width = req.width;
6270 		}
6271 		state = newst;
6272 		if ((rwidth < awidth) ^ (slot->width <= awidth)) break;
6273 	}
6274 	while ((sd->r_menu[state].width > awidth) && sd->r_menu[state + 1].slot)
6275 		state++;
6276 	if (state != sd->r_menu_state)
6277 	{
6278 		if (!lock) lock = toggle_updates(widget, NULL);
6279 		change_to_state(sd, state);
6280 		child->requisition.width = sd->r_menu[state].width;
6281 	}
6282 	if (state != oldst) switch_states(sd, state, oldst);
6283 	if (lock) toggle_updates(widget, lock);
6284 }
6285 
smart_menu_size_req(GtkWidget * widget,GtkRequisition * req,gpointer user_data)6286 static void smart_menu_size_req(GtkWidget *widget, GtkRequisition *req,
6287 	gpointer user_data)
6288 {
6289 	smartmenu_data *sd = user_data;
6290 	GtkRequisition child_req;
6291 	GtkWidget *child;
6292 	int fullw;
6293 
6294 	req->width = req->height = GTK_CONTAINER(widget)->border_width * 2;
6295 	if (!GTK_BOX(widget)->children) return;
6296 	child = BOX_CHILD_0(widget);
6297 	if (!GTK_WIDGET_VISIBLE(child)) return;
6298 
6299 	gtk_widget_size_request(child, &child_req);
6300 	fullw = smart_menu_full_width(sd, widget, child_req.width);
6301 
6302 	req->width += fullw;
6303 	req->height += child_req.height;
6304 }
6305 
smart_menu_size_alloc(GtkWidget * widget,GtkAllocation * alloc,gpointer user_data)6306 static void smart_menu_size_alloc(GtkWidget *widget, GtkAllocation *alloc,
6307 	gpointer user_data)
6308 {
6309 	smartmenu_data *sd = user_data;
6310 	GtkRequisition child_req;
6311 	GtkAllocation child_alloc;
6312 	GtkWidget *child;
6313 	int border = GTK_CONTAINER(widget)->border_width, border2 = border * 2;
6314 
6315 	widget->allocation = *alloc;
6316 	if (!GTK_BOX(widget)->children) return;
6317 	child = BOX_CHILD_0(widget);
6318 	if (!GTK_WIDGET_VISIBLE(child)) return;
6319 
6320 	/* Maybe recursive calls to this cannot happen, but if they can,
6321 	 * crash would be quite spectacular - so, better safe than sorry */
6322 	if (sd->in_alloc) /* Postpone reaction */
6323 	{
6324 		sd->in_alloc |= 2;
6325 		return;
6326 	}
6327 
6328 	/* !!! Always keep child widget requisition set according to its
6329 	 * !!! mode, or this code will break down in interesting ways */
6330 	gtk_widget_get_child_requisition(child, &child_req);
6331 /* !!! Alternative approach - reliable but slow */
6332 //	gtk_widget_size_request(child, &child_req);
6333 	while (TRUE)
6334 	{
6335 		sd->in_alloc = 1;
6336 		child_alloc.x = alloc->x + border;
6337 		child_alloc.y = alloc->y + border;
6338 		child_alloc.width = alloc->width > border2 ?
6339 			alloc->width - border2 : 0;
6340 		child_alloc.height = alloc->height > border2 ?
6341 			alloc->height - border2 : 0;
6342 		if ((child_alloc.width != child->allocation.width) &&
6343 			(sd->r_menu_state > 0 ?
6344 			child_alloc.width != child_req.width :
6345 			child_alloc.width < child_req.width))
6346 			smart_menu_state_to_width(sd, widget, child_req.width,
6347 				child_alloc.width);
6348 		if (sd->in_alloc < 2) break;
6349 		alloc = &widget->allocation;
6350 	}
6351 	sd->in_alloc = 0;
6352 
6353 	gtk_widget_size_allocate(child, &child_alloc);
6354 }
6355 
6356 #endif
6357 
6358 /* Fill smart menu structure */
6359 // !!! With inlining this, problem also
smartmenu_done(void ** tbar,void ** r)6360 void *smartmenu_done(void **tbar, void **r)
6361 {
6362 #if GTK_MAJOR_VERSION == 3
6363 	GtkWidget *tl = gtk_label_new("");
6364 	char c, *ts, *src, *dest;
6365 #endif
6366 	smartmenu_data *sd = tbar[2];
6367 	GtkWidget *parent, *item;
6368 	void **rr;
6369 	char *s;
6370 	int i, l, n = 0;
6371 
6372 	/* Find items */
6373 	for (rr = tbar; rr - r < 0; rr = NEXT_SLOT(rr))
6374 	{
6375 		if (GET_OP(rr) != op_SSUBMENU) continue;
6376 		sd->r_menu[n++].slot = rr;
6377 	}
6378 
6379 	/* Setup overflow submenu */
6380 	parent = gtk_menu_item_get_submenu(GTK_MENU_ITEM(sd->r_menu[--n].slot[0]));
6381 	for (i = 0; i < n; i++)
6382 	{
6383 		sd->r_menu[i].fallback = item = gtk_menu_item_new_with_label("");
6384 		gtk_container_add(GTK_CONTAINER(parent), item);
6385 		rr = sd->r_menu[i].slot[1];
6386 
6387 		l = strspn(s = rr[1], "/");
6388 		if (s[l]) s = _(s); // Translate
6389 		s += l;
6390 #if GTK_MAJOR_VERSION == 3
6391 		/* Due to crippled API, cannot set & display a mnemonic without it
6392 		 * attaching in the regular way; need to strip them from items */
6393 		gtk_label_set_text_with_mnemonic(GTK_LABEL(tl), s);
6394 		sd->r_menu[i].key = gtk_label_get_mnemonic_keyval(GTK_LABEL(tl));
6395 		ts = strdup(s);
6396 		src = dest = ts;
6397 		while (TRUE)
6398 		{
6399 			c = *src++;
6400 			if (c != '_') *dest++ = c;
6401 			if (!c) break;
6402 		}
6403 		gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(item))), ts);
6404 		free(ts);
6405 #else /* #if GTK_MAJOR_VERSION <= 2 */
6406 		sd->r_menu[i].key = gtk_label_parse_uline(
6407 			GTK_LABEL(GTK_BIN(item)->child), s);
6408 #endif
6409 	}
6410 	for (i = 0; i <= n / 2; i++) // Swap ends
6411 	{
6412 		r_menu_slot tmp = sd->r_menu[i];
6413 		sd->r_menu[i] = sd->r_menu[n - i];
6414 		sd->r_menu[n - i] = tmp;
6415 	}
6416 	gtk_widget_hide(sd->r_menu[0].slot[0]);
6417 
6418 #if GTK_MAJOR_VERSION == 3
6419 	g_object_ref_sink(tl);
6420 	g_object_unref(tl);
6421 #endif
6422 	return (sd);
6423 }
6424 
6425 #if GTK_MAJOR_VERSION <= 2
6426 
6427 /* Heightbar: increase own height to max height of invisible neighbors */
6428 
6429 typedef struct {
6430 	GtkWidget *self;
6431 	GtkRequisition *req;
6432 } heightbar_data;
6433 
heightbar_req(GtkWidget * widget,gpointer data)6434 static void heightbar_req(GtkWidget *widget, gpointer data)
6435 {
6436 	heightbar_data *hd = data;
6437 	GtkRequisition req;
6438 
6439 	if (widget == hd->self) return; // Avoid recursion
6440 	if (GTK_WIDGET_VISIBLE(widget)) return; // Let GTK+ handle it, for now
6441 	gtk_widget_size_request(widget, &req); // Ask what invisible ones want
6442 	if (req.height > hd->req->height) hd->req->height = req.height;
6443 }
6444 
heightbar_size_req(GtkWidget * widget,GtkRequisition * req,gpointer user_data)6445 static void heightbar_size_req(GtkWidget *widget, GtkRequisition *req,
6446 	gpointer user_data)
6447 {
6448 	heightbar_data hd = { widget, req };
6449 	if (widget->parent) gtk_container_foreach(GTK_CONTAINER(widget->parent),
6450 		(GtkCallback)heightbar_req, &hd);
6451 }
6452 
6453 #endif
6454 
6455 /* Get/set window position & size from/to inifile */
rw_pos(v_dd * vdata,int set)6456 void rw_pos(v_dd *vdata, int set)
6457 {
6458 	char name[128];
6459 	int i, l = strlen(vdata->ininame);
6460 
6461 	memcpy(name, vdata->ininame, l);
6462 	name[l++] = '_'; name[l + 1] = '\0';
6463 	for (i = 0; i < 5; i++)
6464 	{
6465 		name[l] = "xywhm"[i];
6466 		if (set) inifile_set_gint32(name, vdata->xywh[i]);
6467 		else if (vdata->xywh[i] || (i < 2) || (i > 3)) // 0 means auto-size
6468 			vdata->xywh[i] = inifile_get_gint32(name, vdata->xywh[i]);
6469 	}
6470 }
6471 
get_wrap(void ** slot)6472 static GtkWidget *get_wrap(void **slot)
6473 {
6474 	GtkWidget *w = slot[0];
6475 	int op = GET_OP(slot);
6476 	if ((op == op_SPINSLIDE) || (op == op_SPINSLIDEa) ||
6477 //		(op == op_CANVASIMG) || (op == op_CANVASIMGB) || // Leave frame be
6478 		(op == op_PATHs) || (op == op_PATH) || (op == op_TEXT))
6479 		w = gtk_widget_get_parent(w);
6480 	return (w);
6481 }
6482 
6483 /* More specialized packing modes */
6484 
6485 enum {
6486 	pk_PACKEND1 = pk_LAST,
6487 	pk_TABLE0p,
6488 	pk_TABLEp,
6489 	pk_SCROLLVP,
6490 	pk_SCROLLVPv,
6491 	pk_SCROLLVPm,
6492 	pk_SCROLLVPn,
6493 	pk_CONT,
6494 	pk_BIN,
6495 	pk_SHOW,	/* No packing needed, just show the widget */
6496 	pk_UNREAL,	/* Pseudo widget - no packing, just finish creation */
6497 	pk_UNREALV,	/* Pseudo widget with int value */
6498 };
6499 
6500 #define pk_MASK     0xFF
6501 #define pkf_PARENT 0x100
6502 #define pkf_CANVAS 0x200
6503 
6504 /* Make code not compile where it cannot run */
6505 typedef char Too_Many_Packing_Modes[2 * (pk_LAST <= WB_PKMASK + 1) - 1];
6506 
6507 /* Packing modifiers */
6508 
6509 typedef struct {
6510 	int cw;
6511 	int minw, minh;
6512 	int maxw, maxh;
6513 	int wantmax;
6514 } pkmods;
6515 
6516 /* Prepare widget for packing according to settings */
do_prepare(GtkWidget * widget,int pk,pkmods * mods)6517 GtkWidget *do_prepare(GtkWidget *widget, int pk, pkmods *mods)
6518 {
6519 	/* Show this */
6520 	if (pk) gtk_widget_show(widget);
6521 	/* Unwrap this */
6522 	if (pk & pkf_PARENT)
6523 		while (gtk_widget_get_parent(widget))
6524 			widget = gtk_widget_get_parent(widget);
6525 	/* Border this */
6526 	if (mods->cw) gtk_container_set_border_width(GTK_CONTAINER(widget), mods->cw);
6527 #if GTK_MAJOR_VERSION == 3
6528 	/* Set fixed width/height for this */
6529 /* !!! The call below does MINIMUM size; later, separate out the cases where UPPER
6530  * limit is desired, make a wrapper for that, and use maxw/maxh for them */
6531 	if ((mods->minw > 0) || (mods->minh > 0))
6532 		gtk_widget_set_size_request(widget,
6533 			mods->minw > 0 ? mods->minw : -1,
6534 			mods->minh > 0 ? mods->minh : -1);
6535 	/* And/or min ones; this, GTK+3 can do naturally */
6536 	if ((mods->minw < 0) || (mods->minh < 0))
6537 		gtk_widget_set_size_request(widget,
6538 			mods->minw < 0 ? -mods->minw : -1,
6539 			mods->minh < 0 ? -mods->minh : -1);
6540 	/* Make this scrolled window request max size */
6541 	if (mods->wantmax)
6542 	{
6543 #if GTK3VERSION >= 22
6544 		/* Use the new functions */
6545 		if (!((mods->wantmax - 1) & 1))
6546 			gtk_scrolled_window_set_propagate_natural_width(
6547 				GTK_SCROLLED_WINDOW(widget), TRUE);
6548 		if (!((mods->wantmax - 1) & 2))
6549 			gtk_scrolled_window_set_propagate_natural_height(
6550 				GTK_SCROLLED_WINDOW(widget), TRUE);
6551 #else
6552 		/* Add a wrapper doing it */
6553 		GtkWidget *wrap = wjsizebin_new(G_CALLBACK(do_wantmax), NULL,
6554 			(gpointer)(mods->wantmax - 1));
6555 		gtk_widget_show(wrap);
6556 		gtk_container_add(GTK_CONTAINER(wrap), widget);
6557 		widget = wrap;
6558 #endif
6559 	}
6560 #else /* #if GTK_MAJOR_VERSION <= 2 */
6561 	/* Set fixed width/height for this */
6562 	if ((mods->minw > 0) || (mods->minh > 0))
6563 		gtk_widget_set_usize(widget,
6564 			mods->minw > 0 ? mods->minw : -2,
6565 			mods->minh > 0 ? mods->minh : -2);
6566 	/* And/or min ones */
6567 // !!! For now, always use wrapper
6568 	if ((mods->minw < 0) || (mods->minh < 0))
6569 		widget = widget_align_minsize(widget,
6570 			mods->minw < 0 ? -mods->minw : -2,
6571 			mods->minh < 0 ? -mods->minh : -2);
6572 	/* Make this scrolled window request max size */
6573 	if (mods->wantmax) gtk_signal_connect(GTK_OBJECT(widget), "size_request",
6574 		GTK_SIGNAL_FUNC(scroll_max_size_req), (gpointer)(mods->wantmax - 1));
6575 #endif
6576 
6577 	return (widget);
6578 }
6579 
6580 /* Container stack */
6581 
6582 typedef struct {
6583 	void *widget;
6584 	int group; // in inifile
6585 	int type; // how to stuff things into it
6586 } ctslot;
6587 
6588 /* Types of containers */
6589 
6590 enum {
6591 	ct_NONE = 0,
6592 	ct_BOX,
6593 	ct_TABLE,
6594 	ct_CONT, /* Generic container, like menubar and menu */
6595 	ct_BIN,
6596 	ct_SCROLL,
6597 	ct_TBAR,
6598 	ct_NBOOK,
6599 	ct_HVSPLIT,
6600 	ct_SGROUP,
6601 	ct_SMARTMENU,
6602 };
6603 
6604 /* !!! Limited to rows & columns 0-255 per wh composition l16:col8:row8 */
table_it(ctslot * ct,GtkWidget * it,int wh,int pad,int pack)6605 static void table_it(ctslot *ct, GtkWidget *it, int wh, int pad, int pack)
6606 {
6607 	int row = wh & 255, column = (wh >> 8) & 255, l = (wh >> 16) + 1;
6608 	int r0 = (ct->type >> 8) & 255, r1 = ct->type >> 16;
6609 
6610 	/* Track height of columns 0 & 1 in bytes 1 & 2 of type field */
6611 	if (!column && (r0 <= row)) r0 = row + 1;
6612 	if ((column <= 1) && (column + l > 1) && (r1 <= row)) r1 = row + 1;
6613 	ct->type = (r1 << 16) + (r0 << 8) + (ct->type & 255);
6614 
6615 	gtk_table_attach(GTK_TABLE(ct->widget), it, column, column + l, row, row + 1,
6616 		pack == pk_TABLEx ? GTK_EXPAND | GTK_FILL : GTK_FILL, 0,
6617 		pack == pk_TABLEp ? pad : 0, pad);
6618 }
6619 
6620 /* Pack widget into container according to settings */
do_pack(GtkWidget * widget,ctslot * ct,void ** pp,int n,int tpad)6621 int do_pack(GtkWidget *widget, ctslot *ct, void **pp, int n, int tpad)
6622 {
6623 	GtkScrolledWindow *sw;
6624 	GtkAdjustment *adj = adj;
6625 	GtkWidget *box = ct->widget;
6626 	int what = ct->type & 255;
6627 	int l = WB_GETLEN((int)pp[0]);
6628 
6629 #if GTK_MAJOR_VERSION == 3
6630 	/* Apply size group */
6631 	if (what == ct_SGROUP)
6632 	{
6633 		gtk_size_group_add_widget((GtkSizeGroup *)box, widget);
6634 		/* Real container is above it */
6635 		ct++;
6636 		box = ct->widget;
6637 		what = ct->type & 255;
6638 	}
6639 #endif
6640 
6641 	/* Remember what & when goes into HVSPLIT */
6642 	if (what == ct_HVSPLIT)
6643 	{
6644 		hvsplit_data *hd = (void *)ct->widget;
6645 		box = hd->box;
6646 		if (hd->cnt < HVSPLIT_MAX) hd->inbox[hd->cnt++] = widget;
6647 	}
6648 
6649 #if GTK_MAJOR_VERSION == 3
6650 	/* Protect canvas widgets from external painting-over, with CSS nodes
6651 	 * or without */
6652 	if ((n & pkf_CANVAS) && GTK_IS_SCROLLED_WINDOW(box))
6653 		css_restyle(box, (gtk3version >= 20 ?
6654 			".mtPaint_cscroll overshoot,.mtPaint_cscroll undershoot"
6655 			" { background:none; }" :
6656 			".mtPaint_cscroll .overshoot,.mtPaint_cscroll .undershoot"
6657 			" { background:none; }"),
6658 			"mtPaint_cscroll", NULL);
6659 #endif
6660 
6661 	n &= pk_MASK; // Strip flags
6662 
6663 	/* Adapt packing mode to container type */
6664 	if (n <= pk_DEF) switch (what)
6665 	{
6666 	case ct_SMARTMENU:
6667 	case ct_CONT: n = pk_CONT; break;
6668 	case ct_BIN: n = pk_BIN; break;
6669 	case ct_SCROLL: n = pk_SCROLLVP; break;
6670 	}
6671 
6672 	switch (n)
6673 	{
6674 	case pk_PACK:
6675 		gtk_box_pack_start(GTK_BOX(box), widget, FALSE, FALSE, tpad);
6676 		break;
6677 	case pk_XPACK: case pk_EPACK:
6678 		gtk_box_pack_start(GTK_BOX(box), widget, TRUE, n != pk_EPACK, tpad);
6679 		break;
6680 	case pk_PACKEND: case pk_PACKEND1:
6681 		gtk_box_pack_end(GTK_BOX(box), widget, FALSE, FALSE, tpad);
6682 		break;
6683 	case pk_TABLE: case pk_TABLEx: case pk_TABLEp:
6684 		table_it(ct, widget, (int)pp[l], tpad, n);
6685 		break;
6686 	case pk_TABLE0p:
6687 		table_it(ct, widget, (ct->type >> 8) & 255, tpad, pk_TABLEp);
6688 		break;
6689 	case pk_TABLE1x:
6690 		table_it(ct, widget, 0x100 + (ct->type >> 16), tpad, pk_TABLEx);
6691 		break;
6692 	case pk_SCROLLVP: case pk_SCROLLVPv: case pk_SCROLLVPm: case pk_SCROLLVPn:
6693 		sw = GTK_SCROLLED_WINDOW(box);
6694 
6695 		gtk_scrolled_window_add_with_viewport(sw, widget);
6696 #ifdef U_LISTS_GTK1
6697 		adj = gtk_scrolled_window_get_vadjustment(sw);
6698 		if ((n == pk_SCROLLVPv) || (n == pk_SCROLLVPm))
6699 			gtk_container_set_focus_vadjustment(GTK_CONTAINER(widget), adj);
6700 		if (n == pk_SCROLLVPm)
6701 		{
6702 			gtk_signal_connect(GTK_OBJECT(widget), "map",
6703 				GTK_SIGNAL_FUNC(list_scroll_in), adj);
6704 		}
6705 #endif
6706 		if (n == pk_SCROLLVPn)
6707 		{
6708 			/* Set viewport to shadowless */
6709 			box = gtk_bin_get_child(GTK_BIN(box));
6710 			gtk_viewport_set_shadow_type(GTK_VIEWPORT(box), GTK_SHADOW_NONE);
6711 			vport_noshadow_fix(box);
6712 		}
6713 		return (1); // unstack
6714 	case pk_CONT: case pk_BIN:
6715 		gtk_container_add(GTK_CONTAINER(box), widget);
6716 		if (n == pk_BIN) return (1); // unstack
6717 		break;
6718 	}
6719 	if (n == pk_PACKEND1) gtk_box_reorder_child(GTK_BOX(box), widget, 1);
6720 	return (0);
6721 }
6722 
6723 /* Remap opcodes for script mode */
in_script(int op,char ** script)6724 static int in_script(int op, char **script)
6725 {
6726 	int r = WB_GETREF(op);
6727 	op &= WB_OPMASK;
6728 	/* No script - leave as is */
6729 	if (!script);
6730 	/* Remap backend-dependent opcodes */
6731 	else if ((op < op_CTL_0) || (op >= op_CTL_LAST))
6732 	{
6733 		int uop = cmds[op] ? cmds[op]->uop : 0;
6734 		op = uop > 0 ? uop : uop < 0 ? op : r ? op_uOP : op_TRIGGER;
6735 	}
6736 	/* No need to connect event handlers in script mode - except DESTROY */
6737 	else if ((op != op_EVT_DESTROY) &&
6738 		(op >= op_EVT_0) && (op <= op_EVT_LAST)) op = op_TRIGGER;
6739 	return (op);
6740 }
6741 
6742 typedef struct {
6743 	int slots;	// total slots
6744 	int top;	// total on-top allocation, in *void's
6745 	int keys;	// keymap slots
6746 	int act;	// ACTMAP slots
6747 } sizedata;
6748 
6749 /* Predict how many _void pointers_ a V-code sequence could need */
6750 // !!! And with inlining this, same problem
predict_size(sizedata * sz,void ** ifcode,char * ddata,char ** script)6751 void predict_size(sizedata *sz, void **ifcode, char *ddata, char **script)
6752 {
6753 	sizedata s;
6754 	void **v, **pp, *rstack[CALL_DEPTH], **rp = rstack;
6755 	int scripted = FALSE;
6756 	int op, opf, ref, uop;
6757 
6758 	memset(&s, 0, sizeof(s));
6759 	while (TRUE)
6760 	{
6761 		opf = op = (int)*ifcode++;
6762 		ifcode = (pp = ifcode) + WB_GETLEN(op);
6763 		s.slots += ref = WB_GETREF(op);
6764 		op = in_script(op, script);
6765 		if (op < op_END_LAST) break; // End
6766 		// Livescript start/stop
6767 		if (!script && (op == op_uOPNAME) && (opf & WB_SFLAG))
6768 			scripted = !ref;
6769 		// Subroutine return
6770 		if (op == op_RET) ifcode = *--rp;
6771 		// Jump or subroutine call
6772 		else if ((op == op_GOTO) || (op == op_CALL))
6773 		{
6774 			if (op == op_CALL) *rp++ = ifcode;
6775 			v = *pp;
6776 			if (opf & WB_FFLAG)
6777 				v = (void *)(ddata + (int)v);
6778 			if (opf & WB_NFLAG) v = *v; // dereference
6779 			ifcode = v;
6780 		}
6781 		// Keymap
6782 		else if (op == op_KEYMAP) s.keys = 1;
6783 		// Allocation
6784 		else if (op == op_ACTMAP) s.act++;
6785 		else
6786 		{
6787 			s.top += VVS((op == op_TALLOC) || (op == op_TCOPY) ?
6788 				*(int *)(ddata + (int)pp[1]) :
6789 				op == op_TLSPINPACK ? TLSPINPACK_SIZE(pp - 1) :
6790 				cmds[op] ? cmds[op]->size : 0);
6791 			/* Nothing else happens to unreferrable things, neither
6792 			 * to those that simulation ignores */
6793 			if (!ref || !cmds[op]) continue;
6794 			uop = cmds[op]->uop;
6795 			// Name for scripting
6796 			if (scripted && (uop > 0))
6797 			{
6798 				s.slots++;
6799 				s.top += VVS(sizeof(swdata));
6800 			}
6801 			// Slot in keymap
6802 			if (s.keys &&
6803 				(((op >= op_uMENU_0) && (op < op_uMENU_LAST)) ||
6804 				((uop >= op_uMENU_0) && (uop < op_uMENU_LAST))))
6805 				s.keys++;
6806 		}
6807 	}
6808 
6809 	if (s.keys) s.top += VVS(KEYMAP_SIZE(s.keys));
6810 	s.top += s.act * ACT_SIZE;
6811 	s.slots += 2; // safety margin
6812 	*sz = s;
6813 }
6814 
6815 static cmdef cmddefs[] = {
6816 	{ op_RGBIMAGE,	sizeof(rgbimage_data) },
6817 	{ op_RGBIMAGEP,	sizeof(rgbimage_data) },
6818 	{ op_CANVASIMG,	sizeof(rgbimage_data) },
6819 	{ op_CANVASIMGB, sizeof(rgbimage_data) },
6820 	{ op_FCIMAGEP,	sizeof(fcimage_data) },
6821 	{ op_KEYBUTTON,	sizeof(keybutton_data) },
6822 	{ op_FONTSEL,	sizeof(fontsel_data), op_uENTRY },
6823 // !!! Beware - COLORLIST is self-reading and uOPTD is not
6824 	{ op_COLORLIST,	sizeof(colorlist_data), op_uOPTD },
6825 	{ op_COLORLISTN, sizeof(colorlist_data), op_uLISTCC },
6826 	{ op_GRADBAR,	sizeof(gradbar_data) },
6827 	{ op_LISTCCr,	sizeof(listcc_data), op_uLISTCC },
6828 	{ op_LISTC,	sizeof(listc_data), op_uLISTC },
6829 	{ op_LISTCd,	sizeof(listc_data), op_uLISTC },
6830 	{ op_LISTCu,	sizeof(listc_data) },
6831 	{ op_LISTCS,	sizeof(listc_data), op_uLISTC },
6832 	{ op_LISTCX,	sizeof(listc_data) },
6833 	{ op_DOCK,	sizeof(dock_data) },
6834 	{ op_HVSPLIT,	sizeof(hvsplit_data) },
6835 #if GTK_MAJOR_VERSION <= 2
6836 	{ op_SMARTTBAR,	sizeof(smarttbar_data), op_uMENUBAR },
6837 #endif
6838 	{ op_SMARTMENU,	sizeof(smartmenu_data), op_uMENUBAR },
6839 	{ op_DRAGDROP,	sizeof(drag_ctx) },
6840 	/* In this, data slot points to dependent widget's wdata */
6841 	{ op_MOUNT,	0, op_uMOUNT },
6842 	/* In these, data slot points to menu/toolbar slot */
6843 	{ op_TBBUTTON,	0, op_uMENUITEM },
6844 	{ op_TBTOGGLE,	0, op_uMENUCHECK },
6845 	{ op_TBRBUTTON,	0, op_uMENURITEM },
6846 	{ op_TBBOXTOG,	0, op_uMENUCHECK },
6847 	{ op_MENUITEM,	0, op_uMENUITEM },
6848 	{ op_MENUCHECK,	0, op_uMENUCHECK },
6849 	{ op_MENURITEM,	0, op_uMENURITEM },
6850 
6851 	{ op_WEND,	0, op_uWEND },
6852 	{ op_WSHOW,	0, op_uWSHOW },
6853 	{ op_MAINWINDOW, 0, op_uWINDOW },
6854 	{ op_WINDOW,	0, op_uWINDOW },
6855 	{ op_WINDOWm,	0, op_uWINDOW },
6856 	{ op_FPICKpm,	0, op_uFPICK },
6857 	{ op_TOPVBOX,	0, op_uTOPBOX },
6858 	{ op_TOPVBOXV,	0, op_uTOPBOX },
6859 	{ op_PAGE,	0, op_uFRAME },
6860 	{ op_FRAME,	0, op_uFRAME },
6861 	{ op_LABEL,	0, op_uLABEL },
6862 	{ op_SPIN,	0, op_uSPIN },
6863 	{ op_SPINc,	0, op_uSPIN },
6864 	{ op_FSPIN,	0, op_uFSPIN },
6865 	{ op_SPINa,	0, op_uSPINa },
6866 	{ op_SPINSLIDE,	0, op_uSPIN },
6867 	{ op_SPINSLIDEa, 0, op_uSPINa },
6868 	{ op_CHECK,	0, op_uCHECK },
6869 	{ op_CHECKb,	0, op_uCHECKb },
6870 	{ op_RPACK,	0, op_uRPACK },
6871 	{ op_RPACKD,	0, op_uRPACKD },
6872 	{ op_OPT,	0, op_uOPT },
6873 	{ op_OPTD,	0, op_uOPTD },
6874 	{ op_COMBO,	0, op_uOPT },
6875 	{ op_ENTRY,	0, op_uENTRY },
6876 	{ op_MLENTRY,	0, op_uENTRY },
6877 	{ op_COLOR,	0, op_uCOLOR },
6878 //	and various others between op_OPTD and op_OKBTN
6879 	{ op_OKBTN,	0, op_uOKBTN },
6880 	{ op_BUTTON,	0, op_uBUTTON },
6881 	{ op_TOOLBAR,	0, op_uMENUBAR },
6882 	{ op_ACTMAP,	0, -1 },
6883 	{ op_INSENS,	0, -1 },
6884 
6885 	{ op_uWINDOW,	sizeof(swdata), -1 },
6886 	{ op_uFPICK,	sizeof(swdata), -1 },
6887 	{ op_uTOPBOX,	sizeof(swdata), -1 },
6888 	{ op_uOP,	sizeof(swdata), -1 },
6889 	{ op_uFRAME,	sizeof(swdata), -1 },
6890 	{ op_uLABEL,	sizeof(swdata), -1 },
6891 	{ op_uCHECK,	sizeof(swdata), -1 },
6892 	{ op_uCHECKb,	sizeof(swdata), -1 },
6893 	{ op_uSPIN,	sizeof(swdata), -1 },
6894 	{ op_uFSPIN,	sizeof(swdata), -1 },
6895 	{ op_uSPINa,	sizeof(swdata), -1 },
6896 	{ op_uSCALE,	sizeof(swdata), -1 },
6897 	{ op_uOPT,	sizeof(swdata), -1 },
6898 	{ op_uOPTD,	sizeof(swdata), -1 },
6899 	{ op_uRPACK,	sizeof(swdata), -1 },
6900 	{ op_uRPACKD,	sizeof(swdata), -1 },
6901 	{ op_uENTRY,	sizeof(swdata), -1 },
6902 	{ op_uPATHSTR,	sizeof(swdata), -1 },
6903 	{ op_uCOLOR,	sizeof(swdata), -1 },
6904 	{ op_uLISTCC,	sizeof(swdata), -1 },
6905 	{ op_uLISTC,	sizeof(lswdata), -1 },
6906 	{ op_uOKBTN,	sizeof(swdata), -1 },
6907 	{ op_uBUTTON,	sizeof(swdata), -1 },
6908 	{ op_uMENUBAR,	sizeof(swdata), -1 },
6909 	{ op_uMENUITEM,	sizeof(swdata), -1 },
6910 	{ op_uMENUCHECK, sizeof(swdata), -1 },
6911 	{ op_uMENURITEM, sizeof(swdata), -1 },
6912 	{ op_uMOUNT,	sizeof(swdata), -1 },
6913 	{ op_IDXCOLUMN,	sizeof(swdata), -1 },
6914 	{ op_TXTCOLUMN,	sizeof(swdata), -1 },
6915 	{ op_XTXTCOLUMN, sizeof(swdata), -1 },
6916 	{ op_FILECOLUMN, sizeof(swdata), -1 },
6917 	{ op_CHKCOLUMN,	sizeof(swdata), -1 },
6918 	{ op_uALTNAME,	sizeof(swdata), -1 },
6919 };
6920 
6921 static void do_destroy(void **wdata);
6922 
6923 /* V-code is really simple-minded; it can do 0-tests but no arithmetics, and
6924  * naturally, can inline only constants. Everything else must be prepared either
6925  * in global variables, or in fields of "ddata" structure.
6926  * Parameters of codes should be arrayed in fixed order:
6927  * result location first; table location last; builtin event(s) before that */
6928 
6929 #define DEF_BORDER 5
6930 #define GET_BORDER(T) (borders[op_BOR_##T - op_BOR_0] + DEF_BORDER)
6931 
6932 /* Create a new slot */
6933 #define PREP_SLOT(R,W,D,T) (R)[0] = (W) , (R)[1] = (D) , (R)[2] = (T)
6934 #define ADD_SLOT(R,W,D,T) PREP_SLOT(R, W, D, T) , (R) += VSLOT_SIZE
6935 /* Finalize a prepared slot */
6936 #define FIX_SLOT(R,W) (R)[0] = (W) , (R) += VSLOT_SIZE
6937 
6938 /* Accessors for container stack */
6939 
6940 #define CT_PUSH(SP,W,T)	((SP)-- , (SP)->widget = (W) , (SP)->type = (T) , \
6941 	(SP)->group = keygroup)
6942 #define CT_POP(SP)	((SP)++)
6943 #define CT_DROP(SP)	(keygroup = ((SP)++)->group)
6944 #define CT_TOP(SP)	((SP)->widget)
6945 #define CT_N(SP,N)	((SP)[(N)].widget)
6946 #define CT_WHAT(SP)	((SP)->type & 255)
6947 
run_create_(void ** ifcode,void * ddata,int ddsize,char ** script)6948 void **run_create_(void **ifcode, void *ddata, int ddsize, char **script)
6949 {
6950 	char *ident = VCODE_KEY;
6951 	/* Avoid complex typecast - not initing GType in cmdline mode */
6952 	GtkWindow *tparent = (GtkWindow *)main_window;
6953 #if GTK_MAJOR_VERSION == 1
6954 	int have_sliders = FALSE;
6955 #endif
6956 	int scripted = FALSE, part = FALSE, accel = 0;
6957 	int borders[op_BOR_LAST - op_BOR_0], wpos = GTK_WIN_POS_CENTER;
6958 	ctslot wstack[CONT_DEPTH], *wp = wstack + CONT_DEPTH;
6959 	int keygroup = 0;
6960 	keymap_data *keymap = NULL;
6961 	GtkWidget *window = NULL, *widget = NULL;
6962 	GtkAccelGroup* ag = NULL;
6963 	v_dd *vdata;
6964 	sizedata sz;
6965 	col_data c;
6966 	pkmods mods;
6967 	void *rstack[CALL_DEPTH], **rp = rstack;
6968 	void *v, **pp, **dtail, **r = NULL, **res = NULL, *sw = NULL;
6969 	void **tbar = NULL, **rslot = NULL, *rvar = NULL;
6970 	char *wid = NULL, *gid = NULL;
6971 	int ld, dsize;
6972 	int i, n, op, lp, ref, pk, cw, tpad, ct = 0;
6973 
6974         // Per-command allocations
6975         memset(cmds, 0, sizeof(cmds));
6976         for (i = 0; i < sizeof(cmddefs) / sizeof(cmddefs[0]); i++)
6977                 cmds[cmddefs[i].op] = cmddefs + i;
6978 
6979 	// Allocation size
6980 	predict_size(&sz, ifcode, ddata, script);
6981 	ld = VVS(ddsize);
6982 	n = VVS(sizeof(v_dd));
6983 	dsize = ld + n + ++sz.slots * VSLOT_SIZE + sz.top;
6984 	if (!(res = calloc(dsize, sizeof(void *)))) return (NULL); // failed
6985 	dtail = res + dsize; // Locate tail of block
6986 	memcpy(res, ddata, ddsize); // Copy datastruct
6987 	ddata = res; // And switch to using it
6988 	vdata = (void *)(res += ld); // Locate where internal data go
6989 	r = res += n; // Anchor after it
6990 	vdata->code = WDONE; // Make internal datastruct a noop
6991 	// Allocate actmap
6992 	vdata->actmap = dtail -= sz.act * ACT_SIZE;
6993 	// Store struct ref at anchor, use datastruct as tag for it
6994 	ADD_SLOT(r, ddata, vdata, dtail);
6995 
6996 	// Border sizes are DEF_BORDER-based
6997 	memset(borders, 0, sizeof(borders));
6998 
6999 	// Column data
7000 	memset(&c, 0, sizeof(c));
7001 
7002 	// Packing modifiers
7003 	memset(&mods, 0, sizeof(mods));
7004 
7005 	if (!script) ag = gtk_accel_group_new();
7006 
7007 	while (TRUE)
7008 	{
7009 		op = (int)*ifcode;
7010 		ifcode = (pp = ifcode) + 1 + (lp = WB_GETLEN(op));
7011 		pk = WB_GETPK(op);
7012 		/* Table loc is outside the token proper */
7013 		{
7014 			int p = pk & pk_MASK; // !!! To prevent GCC misoptimizing
7015 			lp -= (p >= pk_TABLE) && (p <= pk_TABLEx);
7016 		}
7017 		v = lp > 0 ? pp[1] : NULL;
7018 		if (op & WB_FFLAG) v = (void *)((char *)ddata + (int)v);
7019 		if (op & WB_NFLAG) v = *(char **)v; // dereference a string
7020 		ref = WB_GETREF(op);
7021 		op = in_script(op, script);
7022 		if (cmds[op]) dtail -= VVS(cmds[op]->size);
7023 		/* Prepare slot, with data pointer in widget's place */
7024 		PREP_SLOT(r, v, pp, dtail);
7025 		tpad = cw = 0;
7026 		gid = NULL;
7027 		switch (op)
7028 		{
7029 		/* Terminate */
7030 		case op_WEND: case op_WSHOW: case op_WDIALOG:
7031 			/* Terminate the list */
7032 			ADD_SLOT(r, NULL, NULL, NULL);
7033 
7034 			/* Apply keymap */
7035 			if (keymap)
7036 			{
7037 				keymap->ag = ag;
7038 				keymap_init(keymap, NULL);
7039 				accel |= 1;
7040 			}
7041 
7042 			/* !!! In GTK+1, doing it earlier makes any following
7043 			 * gtk_widget_add_accelerator() calls to silently fail */
7044 			if (accel > 1) gtk_accel_group_lock(ag);
7045 
7046 			/* Add accel group, or drop it if unused */
7047 			if (!accel) gtk_accel_group_unref(ag);
7048 			else gtk_window_add_accel_group(GTK_WINDOW(window), ag);
7049 
7050 			gtk_object_set_data(GTK_OBJECT(window), ident,
7051 				(gpointer)res);
7052 			gtk_signal_connect_object(GTK_OBJECT(window), "destroy",
7053 				GTK_SIGNAL_FUNC(do_destroy), (gpointer)res);
7054 			/* !!! Freeing the datastruct is best to happen only when
7055 			 * all refs to underlying object are safely dropped */
7056 			gtk_object_weakref(GTK_OBJECT(window),
7057 				(GtkDestroyNotify)free, (gpointer)ddata);
7058 #if GTK_MAJOR_VERSION == 1
7059 			/* To make Smooth theme engine render sliders properly */
7060 			if (have_sliders) gtk_signal_connect(
7061 				GTK_OBJECT(window), "show",
7062 				GTK_SIGNAL_FUNC(gtk_widget_queue_resize), NULL);
7063 #endif
7064 			/* Init actmap to insensitive */
7065 			act_state(vdata, 0);
7066 			/* Add finishing touches to a toplevel */
7067 			if (!part)
7068 			{
7069 				vdata->tparent = tparent;
7070 				/* Trigger remembered events */
7071 				trigger_things(res);
7072 			}
7073 			/* Display */
7074 			if (op != op_WEND) cmd_showhide(GET_WINDOW(res), TRUE);
7075 			/* Dialogs must be immune to stuck pointer grabs */
7076 			if (op == op_WDIALOG)
7077 			{
7078 				// Paranoia
7079 				GtkWidget *grab = gtk_grab_get_current();
7080 				if (grab && (grab != GET_REAL_WINDOW(res)))
7081 					gtk_grab_add(GET_REAL_WINDOW(res));
7082 				// Real concern - a server grab
7083 				release_grab();
7084 			}
7085 			/* Wait for input */
7086 			if (op == op_WDIALOG)
7087 			{
7088 				*(void ***)v = NULL; // clear result slot
7089 				vdata->dv = v; // announce it
7090 				while (!*(void ***)v) gtk_main_iteration();
7091 			}
7092 			/* Return anchor position */
7093 			return (res);
7094 		/* Terminate in script mode */
7095 		case op_uWEND: case op_uWSHOW:
7096 			/* Terminate the list */
7097 			ADD_SLOT(r, NULL, NULL, NULL);
7098 			/* Trigger remembered events */
7099 			if (!part) trigger_things(res);
7100 			/* Init actmap to insensitive */
7101 			act_state(vdata, 0);
7102 			/* Activate */
7103 			if (op != op_uWEND)
7104 				cmd_showhide(GET_WINDOW(res), TRUE);
7105 			/* Return anchor position - maybe already freed */
7106 			return (res);
7107 		/* Script mode fileselector */
7108 		case op_uFPICK:
7109 			((swdata *)dtail)->strs = resolve_path(NULL, PATHBUF, v);
7110 			// Fallthrough
7111 		/* Script mode pseudo window */
7112 		case op_uWINDOW: case op_uTOPBOX:
7113 			part = op == op_uTOPBOX; // not toplevel
7114 			/* Store script ref, to run it when done */
7115 			vdata->script = script;
7116 			wid = ""; // First unnamed field gets to be default
7117 			pk = pk_UNREAL;
7118 			if (op == op_uFPICK) pk = pk_UNREALV;
7119 			break;
7120 		/* Script mode alternate identifier */
7121 		case op_uALTNAME:
7122 			if (!script && !scripted) continue;
7123 			wid = v;
7124 			pk = pk_UNREALV;
7125 			break;
7126 		/* Script mode identifier (forced) */
7127 		case op_uOPNAME:
7128 			/* If flagged as livescript start/stop marker */
7129 			if ((int)pp[0] & WB_SFLAG)
7130 			{
7131 				widget = NULL;
7132 				scripted = FALSE;
7133 				if (ref) break; // stop if ref
7134 				wid = ""; // default
7135 				scripted = !script;
7136 				continue; // start if no ref
7137 			}
7138 			/* If inside script and having something to set */
7139 			if ((script || scripted) && v)
7140 			{
7141 				void **slot = prev_uslot(r);
7142 				if (slot) ((swdata *)slot[0])->id = v;
7143 			}
7144 			wid = NULL; // Clear current identifier
7145 			continue;
7146 		/* Script mode frame, as identifier to next control */
7147 		case op_uFRAME:
7148 			wid = v;
7149 			// Fallthrough
7150 		/* Script mode OK button, as placeholder */
7151 		case op_uOKBTN:
7152 		/* Script mode generic placeholder / group marker */
7153 		case op_uOP:
7154 			pk = pk_UNREAL;
7155 			/* If a group ID and inside script */
7156 			if ((((int)pp[0] & WB_OPMASK) == op_uOP) && lp &&
7157 				(script || scripted))
7158 			{
7159 				void **slot;
7160 				if (v) wid = v;
7161 				if (!wid && (slot = prev_uslot(r)))
7162 					wid = ((swdata *)slot[0])->id;
7163 				pk = pk_UNREALV;
7164 			}
7165 			break;
7166 		/* Script mode button */
7167 		case op_uBUTTON:
7168 			wid = v;
7169 			pk = pk_UNREALV;
7170 			// Not scriptable by default
7171 			if ((int)pp[0] & WB_SFLAG) break;
7172 			op = op_uOP;
7173 			pk = pk_UNREAL; // Leave identifier for next widget
7174 			break;
7175 		/* Script mode checkbox */
7176 		case op_uCHECKb:
7177 			*(int *)v = inifile_get_gboolean(pp[3], *(int *)v);
7178 			// Fallthrough
7179 		case op_uCHECK:
7180 			wid = pp[2];
7181 			tpad = !!*(int *)v;
7182 			pk = pk_UNREALV;
7183 			break;
7184 		/* Script mode spinbutton */
7185 		case op_uSPIN: case op_uFSPIN: case op_uSPINa: case op_uSCALE:
7186 		{
7187 			int a, b;
7188 			if (op != op_uSPINa) a = (int)pp[2] , b = (int)pp[3];
7189 			else a = ((int *)v)[1] , b = ((int *)v)[2];
7190 			((swdata *)dtail)->range[0] = a;
7191 			((swdata *)dtail)->range[1] = b;
7192 			tpad = *(int *)v;
7193 			tpad = tpad > b ? b : tpad < a ? a : tpad;
7194 			if (op == op_uSCALE) // Backup original value
7195 				((swdata *)dtail)->strs = (void *)tpad;
7196 			pk = pk_UNREALV;
7197 			break;
7198 		}
7199 		/* Script mode option pack */
7200 		case op_uOPT: case op_uOPTD: case op_uRPACK: case op_uRPACKD:
7201 		{
7202 			char **strs = pp[2];
7203 			int n = (int)pp[3];
7204 
7205 			if (op == op_uRPACK) n >>= 8;
7206 			else if (op == op_uOPT);
7207 			else /* OPTD/RPACKD */
7208 			{
7209 				strs = *(char ***)((char *)ddata + (int)strs);
7210 				n = 0;
7211 			}
7212 			if (n <= 0) for (n = 0; strs[n]; n++); // Count strings
7213 			((swdata *)dtail)->cnt = n;
7214 			((swdata *)dtail)->strs = strs;
7215 			tpad = *(int *)v;
7216 			if ((tpad >= n) || (tpad < 0) || !strs[tpad][0]) tpad = 0;
7217 			pk = pk_UNREALV;
7218 			break;
7219 		}
7220 		/* Script mode entry - fill from drop-away buffer */
7221 		case op_uENTRY: case op_uPATHSTR:
7222 			// Length limit (not including 0)
7223 			tpad = ((swdata *)dtail)->value = lp > 1 ? (int)pp[2] : -1;
7224 			// Replace transient buffer - it may get freed on return
7225 			*(char **)v = set_uentry((swdata *)dtail, *(char **)v);
7226 			pk = pk_UNREALV;
7227 			break;
7228 		/* Script mode color picker - leave unfilled (?) */
7229 		case op_uCOLOR:
7230 			pk = pk_UNREALV;
7231 			break;
7232 		/* Script mode list with columns */
7233 		case op_uLISTC:
7234 			set_columns(&((lswdata *)dtail)->c, &c, ddata, r);
7235 			((swdata *)dtail)->strs = v; // Pointer to index
7236 			tpad = ulistc_reset(r);
7237 			pk = pk_UNREALV;
7238 			break;
7239 		/* Script mode list */
7240 		case op_uLISTCC:
7241 			((swdata *)dtail)->strs = v; // Pointer to index
7242 			tpad = *(int *)v;
7243 			pk = pk_UNREALV;
7244 			break;
7245 		/* Script mode menubar */
7246 		case op_uMENUBAR:
7247 			tbar = r;
7248 			rvar = rslot = NULL;
7249 			pk = pk_UNREAL;
7250 			break;
7251 		/* Script mode menu item/toggle */
7252 		case op_uMENURITEM:
7253 			/* Chain to previous */
7254 			if (rvar == v) ((swdata *)rslot[0])->range[1] =
7255 				((swdata *)dtail)->range[0] = r - rslot;
7256 			/* Now this represents group */
7257 			rslot = r; rvar = v;
7258 			// Fallthrough
7259 		case op_uMENUCHECK:
7260 			tpad = *(int *)v;
7261 			tpad = op == op_uMENURITEM ? tpad == (int)pp[2] : !!tpad;
7262 			// Fallthrough
7263 		case op_uMENUITEM:
7264 			wid = pp[3];
7265 			((swdata *)dtail)->strs = tbar;
7266 			pk = pk_UNREALV;
7267 			break;
7268 		/* Script mode mount socket */
7269 		case op_uMOUNT:
7270 		{
7271 			void **what = ((mnt_fn)pp[2])(res);
7272 			*(int *)v = TRUE;
7273 			((swdata *)dtail)->strs = what;
7274 			pk = pk_UNREAL;
7275 			break;
7276 		}
7277 		/* Done with a container */
7278 		case op_WDONE:
7279 #if GTK_MAJOR_VERSION == 3
7280 			if (CT_WHAT(wp) == ct_SGROUP) CT_POP(wp);
7281 #endif
7282 			/* Prepare smart menubar when done */
7283 			if (CT_WHAT(wp) == ct_SMARTMENU)
7284 				vdata->smmenu = smartmenu_done(tbar, r);
7285 			CT_DROP(wp);
7286 			continue;
7287 		/* Create the main window */
7288 		case op_MAINWINDOW:
7289 		{
7290 			int wh = (int)pp[3];
7291 
7292 			gdk_rgb_init();
7293 
7294 			init_tablet();	// Set up the tablet
7295 
7296 			widget = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7297 // !!! Better to use WIDTH() and HEIGHT() as elsewhere
7298 			// Set minimum width/height
7299 #if GTK_MAJOR_VERSION == 3
7300 			gtk_widget_set_size_request(window, wh >> 16, wh & 0xFFFF);
7301 			/* Global initialization */
7302 			tool_key = g_quark_from_static_string(TOOL_KEY);
7303 #else
7304 			gtk_widget_set_usize(window, wh >> 16, wh & 0xFFFF);
7305 #endif
7306 			// Set name _without_ translating
7307 			gtk_window_set_title(GTK_WINDOW(window), v);
7308 
7309 	/* !!! If main window receives these events, GTK+ will be able to
7310 	 * direct them to current modal window. Which makes it possible to
7311 	 * close popups by clicking on the main window outside popup - WJ */
7312 			gtk_widget_add_events(window,
7313 				GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
7314 
7315 			// we need to realize the window because we use pixmaps
7316 			// for items on the toolbar & menus in the context of it
7317 			gtk_widget_realize(window);
7318 
7319 #if GTK_MAJOR_VERSION == 1
7320 			{
7321 				GdkPixmap *icon_pix = gdk_pixmap_create_from_xpm_d(
7322 					window->window, NULL, NULL, pp[2]);
7323 				gdk_window_set_icon(window->window, NULL, icon_pix, NULL);
7324 //				gdk_pixmap_unref(icon_pix);
7325 			}
7326 #else /* #if GTK_MAJOR_VERSION >= 2 */
7327 			{
7328 				GdkPixbuf *p = gdk_pixbuf_new_from_xpm_data(pp[2]);
7329 				gtk_window_set_icon(GTK_WINDOW(window), p);
7330 				g_object_unref(p);
7331 			}
7332 #endif
7333 
7334 			// For use as anchor and context
7335 			main_window = window;
7336 
7337 			ct = ct_BIN;
7338 			break;
7339 		}
7340 		/* Create a toplevel window, and put a vertical box inside it */
7341 		case op_WINDOW: case op_WINDOWm:
7342 			vdata->modal = op != op_WINDOW;
7343 			widget = window = add_a_window(GTK_WINDOW_TOPLEVEL,
7344 				*(char *)v ? _(v) : v, wpos);
7345 			sw = add_vbox(window);
7346 			ct = ct_BOX;
7347 			break;
7348 		/* Create a dialog window, with vertical & horizontal box */
7349 		case op_DIALOGm:
7350 			vdata->modal = TRUE;
7351 			widget = window = gtk_dialog_new();
7352 			gtk_window_set_title(GTK_WINDOW(window), _(v));
7353 			gtk_window_set_position(GTK_WINDOW(window), wpos);
7354 // !!! Border = 6
7355 			gtk_container_set_border_width(GTK_CONTAINER(window), 6);
7356 			/* Both boxes go onto stack, with vbox on top */
7357 			CT_PUSH(wp, gtk_dialog_get_action_area(GTK_DIALOG(window)), ct_BOX);
7358 			CT_PUSH(wp, gtk_dialog_get_content_area(GTK_DIALOG(window)), ct_BOX);
7359 			break;
7360 		/* Create a fileselector window (with horizontal box inside) */
7361 		case op_FPICKpm:
7362 		{
7363 			GtkWidget *box;
7364 
7365 			vdata->modal = TRUE;
7366 			widget = window = fpick(&box,
7367 				*(char **)((char *)ddata + (int)pp[2]),
7368 				*(int *)((char *)ddata + (int)pp[3]), r);
7369 #ifdef U_FPICK_GTKFILESEL
7370 			add_del(SLOT_N(r, 2), window);
7371 #endif
7372 			sw = box;
7373 			ct = ct_BOX;
7374 			/* Initialize */
7375 			fpick_set_filename(window, v, FALSE);
7376 			break;
7377 		}
7378 		/* Create a popup window */
7379 		case op_POPUP:
7380 			vdata->modal = TRUE;
7381 			widget = window = add_a_window(GTK_WINDOW_POPUP,
7382 				*(char *)v ? _(v) : v, wpos);
7383 			cw = GET_BORDER(POPUP);
7384 			ct = ct_BIN;
7385 			break;
7386 		/* Create a vbox which will serve as separate widget */
7387 		case op_TOPVBOX:
7388 			part = TRUE; // not toplevel
7389 			widget = window = vbox_new(0);
7390 			cw = GET_BORDER(TOPVBOX);
7391 			ct = ct_BOX;
7392 			pk = pk_SHOW;
7393 			break;
7394 		/* Create a widget vbox with special sizing behaviour */
7395 		case op_TOPVBOXV:
7396 			part = TRUE; // not toplevel
7397 			// Fill space vertically but not horizontally
7398 			widget = window = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
7399 			// Keep max vertical size
7400 			widget_set_keepsize(window, TRUE);
7401 			sw = add_vbox(window);
7402 			ct = ct_BOX;
7403 			gtk_container_set_border_width(GTK_CONTAINER(sw),
7404 				GET_BORDER(TOPVBOX));
7405 			pk = pk_SHOW;
7406 			break;
7407 		/* Add a dock widget */
7408 		case op_DOCK:
7409 		{
7410 			GtkWidget *p0, *p1, *pane;
7411 			dock_data *dd = (void *)dtail;
7412 
7413 			widget = hbox_new(0);
7414 			gtk_widget_show(widget);
7415 
7416 			/* First, create the dock pane - hidden for now */
7417 			dd->pane = pane = gtk_hpaned_new();
7418 			paned_mouse_fix(pane);
7419 			gtk_box_pack_end(GTK_BOX(widget), pane, TRUE, TRUE, 0);
7420 
7421 			/* Create the right pane */
7422 			p1 = vbox_new(0);
7423 			gtk_widget_show(p1);
7424 			gtk_paned_pack2(GTK_PANED(pane), p1, FALSE, TRUE);
7425 #if GTK_MAJOR_VERSION == 1
7426 	/* !!! Hack - but nothing else seems to prevent a sorry mess when
7427 	 * a widget gets REMOUNT'ed from a never-yet displayed pane */
7428 			gtk_container_set_resize_mode(GTK_CONTAINER(p1),
7429 				GTK_RESIZE_QUEUE);
7430 #endif
7431 
7432 			/* Now, create the left pane - for now, separate */
7433 			dd->vbox = p0 = xpack(widget, vbox_new(0));
7434 			gtk_widget_show(p0);
7435 
7436 			/* Pack everything */
7437 			if (do_pack(widget, wp, pp, pk, tpad)) CT_POP(wp);
7438 			CT_PUSH(wp, p1, ct_BOX); // right page second
7439 			CT_PUSH(wp, p0, ct_BOX); // left page first
7440 // !!! Maybe pk_SHOW ?
7441 			pk = 0;
7442 			break;
7443 		}
7444 		/* Add a horizontal/vertical split widget */
7445 		case op_HVSPLIT:
7446 		{
7447 			GtkWidget *p;
7448 			hvsplit_data *hd = (void *)dtail;
7449 
7450 			hd->box = widget = vbox_new(0);
7451 
7452 			/* Create the two panes - hidden for now */
7453 			hd->panes[0] = p = gtk_hpaned_new();
7454 			paned_mouse_fix(p);
7455 			gtk_box_pack_end(GTK_BOX(widget), p, TRUE, TRUE, 0);
7456 			hd->panes[1] = p = gtk_vpaned_new();
7457 			paned_mouse_fix(p);
7458 			gtk_box_pack_end(GTK_BOX(widget), p, TRUE, TRUE, 0);
7459 
7460 			sw = hd; // Datastruct in place of widget
7461 			ct = ct_HVSPLIT;
7462 			break;
7463 		}
7464 		/* Add a notebook page */
7465 		case op_PAGE: case op_PAGEi:
7466 		{
7467 			GtkWidget *label = op == op_PAGE ?
7468 				gtk_label_new(_(v)) : xpm_image(v);
7469 			gtk_widget_show(label);
7470 			widget = vbox_new(op == op_PAGE ? 0 : (int)pp[2]);
7471 			gtk_notebook_append_page(GTK_NOTEBOOK(CT_TOP(wp)),
7472 				widget, label);
7473 			ct = ct_BOX;
7474 			pk = pk_SHOW;
7475 			break;
7476 		}
7477 		/* Add a table */
7478 		case op_TABLE:
7479 			widget = gtk_table_new((int)v & 0xFFFF, (int)v >> 16, FALSE);
7480 			if (lp > 1)
7481 			{
7482 				int s = (int)pp[2];
7483 				gtk_table_set_row_spacings(GTK_TABLE(widget), s);
7484 				gtk_table_set_col_spacings(GTK_TABLE(widget), s);
7485 			}
7486 // !!! Padding = 0
7487 			cw = GET_BORDER(TABLE);
7488 			ct = ct_TABLE;
7489 			break;
7490 		/* Add an equal-spaced horizontal box */
7491 		case op_EQBOX:
7492 		/* Add a box */
7493 		case op_VBOX: case op_HBOX:
7494 #if GTK_MAJOR_VERSION == 3
7495 			widget = gtk_box_new((op == op_VBOX ? GTK_ORIENTATION_VERTICAL :
7496 				GTK_ORIENTATION_HORIZONTAL), (int)v & 255);
7497 			gtk_box_set_homogeneous(GTK_BOX(widget), op == op_EQBOX);
7498 #else
7499 			widget = (op == op_VBOX ? gtk_vbox_new :
7500 				gtk_hbox_new)(op == op_EQBOX, (int)v & 255);
7501 #endif
7502 			if (lp)
7503 			{
7504 				cw = ((int)v >> 8) & 255;
7505 				tpad = ((int)v >> 16) & 255;
7506 			}
7507 			ct = ct_BOX;
7508 			break;
7509 		/* Add a frame */
7510 		case op_FRAME:
7511 			wid = v;
7512 			// Fallthrough
7513 		case op_EFRAME:
7514 			widget = gtk_frame_new(v && *(char *)v ? _(v) : v);
7515 			if (op == op_EFRAME) gtk_frame_set_shadow_type(
7516 				GTK_FRAME(widget), GTK_SHADOW_ETCHED_OUT);
7517 // !!! Padding = 0
7518 			cw = GET_BORDER(FRAME);
7519 			ct = ct_BIN;
7520 			break;
7521 		/* Add a scrolled window */
7522 		case op_SCROLL:
7523 			widget = scrollw((int)v);
7524 			tpad = GET_BORDER(SCROLL);
7525 			ct = ct_SCROLL;
7526 			break;
7527 		/* Add a control-like scrolled window */
7528 		case op_CSCROLL:
7529 		{
7530 			int *xp = v;
7531 			xp[0] = xp[1] = 0; // initial position
7532 			widget = scrollw(0x101); // auto/auto
7533 // !!! Padding = 0 Border = 0
7534 			ct = ct_SCROLL;
7535 			break;
7536 		}
7537 		/* Add a normal notebook */
7538 		case op_NBOOKl: case op_NBOOK:
7539 			widget = gtk_notebook_new();
7540 			if (op == op_NBOOKl) gtk_notebook_set_tab_pos(
7541 				GTK_NOTEBOOK(widget), GTK_POS_LEFT);
7542 			if ((CT_WHAT(wp) == ct_SCROLL) && (pk <= pk_DEF))
7543 				pk = pk_SCROLLVPn; // no border
7544 // !!! Padding = 0
7545 			cw = GET_BORDER(NBOOK);
7546 			ct = ct_NBOOK;
7547 			break;
7548 		/* Add a plain notebook */
7549 		case op_PLAINBOOK:
7550 		{
7551 			int n = v ? (int)v : 2; // 2 pages by default
7552 
7553 			widget = gtk_notebook_new();
7554 			gtk_notebook_set_show_tabs(GTK_NOTEBOOK(widget), FALSE);
7555 			gtk_notebook_set_show_border(GTK_NOTEBOOK(widget), FALSE);
7556 
7557 // !!! Border = 0
7558 			if (do_pack(widget, wp, pp, pk, tpad)) CT_POP(wp);
7559 			while (n-- > 0)
7560 			{
7561 				GtkWidget *page = vbox_new(0);
7562 				gtk_notebook_prepend_page(GTK_NOTEBOOK(widget),
7563 					page, NULL); // stack pages back to front
7564 				CT_PUSH(wp, page, ct_BOX);
7565 			}
7566 			gtk_widget_show_all(widget);
7567 			pk = 0;
7568 			break;
7569 		}
7570 		/* Add a toggle button for controlling 2-paged notebook */
7571 		case op_BOOKBTN:
7572 			widget = gtk_toggle_button_new_with_label(_(pp[2]));
7573 			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
7574 			gtk_signal_connect(GTK_OBJECT(widget), "toggled",
7575 				GTK_SIGNAL_FUNC(toggle_vbook), v);
7576 // !!! Padding = 0
7577 			cw = GET_BORDER(BUTTON);
7578 			break;
7579 		/* Add a statusbar box */
7580 		case op_STATUSBAR:
7581 		{
7582 			GtkWidget *w, *label = gtk_label_new("");
7583 			GtkRequisition req;
7584 
7585 			w = widget = hbox_new(0);
7586 #if GTK_MAJOR_VERSION == 3
7587 			/* Do not let statusbar crowd anything else (unnecessary
7588 			 * on GTK+1&2 due to different sizing method there) */
7589 			w = wjsizebin_new(G_CALLBACK(do_shorten), NULL, label);
7590 			gtk_widget_show(w);
7591 			gtk_container_add(GTK_CONTAINER(w), widget);
7592 #endif
7593 		/* !!! The following is intended to give enough height to the bar
7594 		 * even in case no items are shown. It depends on GTK+ setting
7595 		 * proper height (and zero width) for a label containing an empty
7596 		 * string. And size fixing isn't sure to set the right value if
7597 		 * the toplevel isn't yet realized (unlike MAINWINDOW) - WJ */
7598 			if (do_pack(w, wp, pp, pk, tpad)) CT_POP(wp);
7599 			pack(widget, label);
7600 			gtk_widget_show(label);
7601 			/* To prevent statusbar wobbling */
7602 #if GTK_MAJOR_VERSION == 3 /* !!! May need keepsize or maxsize */
7603 			gtk_widget_get_preferred_size(widget, &req, NULL);
7604 			gtk_widget_set_size_request(widget, -1, req.height);
7605 #else /* if GTK_MAJOR_VERSION <= 2 */
7606 			gtk_widget_size_request(widget, &req);
7607 			gtk_widget_set_usize(widget, -1, req.height);
7608 #endif
7609 
7610 			ct = ct_BOX;
7611 			pk = pk_SHOW;
7612 			break;
7613 		}
7614 		/* Add a statusbar label */
7615 		case op_STLABEL:
7616 		{
7617 			int paw = (int)v;
7618 			widget = gtk_label_new("");
7619 			gtk_misc_set_alignment(GTK_MISC(widget),
7620 				((paw >> 16) & 255) / 2.0, 0.0);
7621 			if (paw & 0xFFFF) mods.minw = paw & 0xFFFF; // usize
7622 			// Label-specific packing
7623 			if (pk == pk_PACKEND) pk = pk_PACKEND1;
7624 // !!! Padding = 0 Border = 0
7625 			break;
7626 		}
7627 		/* Add a horizontal line */
7628 		case op_HSEP:
7629 			widget = gtk_hseparator_new();
7630 			if ((int)v >= 0) // usize
7631 			{
7632 				if (lp) mods.minw = (int)v;
7633 // !!! Height = 10
7634 				mods.minh = 10;
7635 			}
7636 // !!! Padding = 0
7637 			break;
7638 		/* Add a label */
7639 		case op_LABEL: case op_uLABEL:
7640 		{
7641 			char *wi0 = wid;
7642 			wid = v;
7643 			// Maybe the preceding slot needs a label
7644 			if (!wi0 && (script || scripted))
7645 			{
7646 				void **slot = prev_uslot(r);
7647 				if (slot)
7648 				{
7649 					swdata *sd = slot[0];
7650 					if (!sd->id && (sd->op != op_uOP))
7651 						sd->id = wid , wid = NULL;
7652 				}
7653 			}
7654 			if (op == op_uLABEL) // Script mode label
7655 			{
7656 				pk = pk_UNREAL;
7657 				break;
7658 			}
7659 			// Fallthrough
7660 		}
7661 		case op_WLABEL:
7662 		{
7663 			int z = lp > 1 ? (int)pp[2] : 0;
7664 			widget = gtk_label_new(*(char *)v ? _(v) : v);
7665 			if (z & 0xFFFF) gtk_misc_set_padding(GTK_MISC(widget),
7666 				(z >> 8) & 255, z & 255);
7667 			gtk_label_set_justify(GTK_LABEL(widget), GTK_JUSTIFY_LEFT);
7668 			gtk_misc_set_alignment(GTK_MISC(widget),
7669 				(z >> 16) / 10.0, 0.5);
7670 			tpad = GET_BORDER(LABEL);
7671 			// Label-specific packing
7672 			if (pk == pk_TABLE1x) pk = pk_TABLE0p;
7673 			if (pk == pk_TABLE) pk = pk_TABLEp;
7674 			if (op == op_WLABEL)
7675 			{
7676 #if GTK_MAJOR_VERSION == 3
7677 				/* Code to keep wrapping width sane is absent in
7678 				 * GTK+3, need be re-added as a wrapper */
7679 				GtkWidget *wrap = wjsizebin_new(G_CALLBACK(do_rewrap), NULL, NULL);
7680 				gtk_widget_show(wrap);
7681 				gtk_container_add(GTK_CONTAINER(wrap), widget);
7682 				pk |= pkf_PARENT;
7683 #endif
7684 				gtk_label_set_line_wrap(GTK_LABEL(widget), TRUE);
7685 			}
7686 			break;
7687 		}
7688 		/* Add a helptext label */
7689 		case op_HLABEL: case op_HLABELm:
7690 			widget = gtk_label_new(v);
7691 #if GTK_MAJOR_VERSION == 3
7692 			gtk_widget_set_can_focus(widget, TRUE);
7693 #else
7694 			GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_FOCUS);
7695 #endif
7696 #if GTK_MAJOR_VERSION >= 2
7697 			gtk_label_set_selectable(GTK_LABEL(widget), TRUE);
7698 #endif
7699 #if GTK_MAJOR_VERSION == 3
7700 			/* "font-size" stopped being broken only in GTK+ 3.22, and
7701 			 * nagging about "Pango syntax" started - WJ */
7702 			if (op == op_HLABELm) css_restyle(widget, (gtk3version >= 22 ?
7703 				".mtPaint_hlabel { font-family: Monospace; font-size:9pt; }" :
7704 				".mtPaint_hlabel { font: Monospace 9; }" ),
7705 				"mtPaint_hlabel", NULL);
7706 #elif GTK_MAJOR_VERSION == 2
7707 			if (op == op_HLABELm)
7708 			{
7709 				PangoFontDescription *pfd =
7710 					pango_font_description_from_string("Monospace 9");
7711 					// Courier also works
7712 				gtk_widget_modify_font(widget, pfd);
7713 				pango_font_description_free(pfd);
7714 			}
7715 #endif
7716 			gtk_label_set_justify(GTK_LABEL(widget), GTK_JUSTIFY_LEFT);
7717 			gtk_label_set_line_wrap(GTK_LABEL(widget), TRUE);
7718 			gtk_misc_set_alignment(GTK_MISC(widget), 0, 0);
7719 // !!! Padding = 5/5
7720 			gtk_misc_set_padding(GTK_MISC(widget), 5, 5);
7721 			break;
7722 		/* Add to table a batch of labels generated from text string */
7723 		case op_TLTEXT:
7724 			tltext(v, pp, CT_TOP(wp), GET_BORDER(LABEL));
7725 			pk = 0;
7726 			break;
7727 		/* Add a progressbar */
7728 		case op_PROGRESS:
7729 			widget = gtk_progress_bar_new();
7730 #if GTK_MAJOR_VERSION == 3
7731 			gtk_progress_bar_set_text(GTK_PROGRESS_BAR(widget), _(v));
7732 #else /* if GTK_MAJOR_VERSION <= 2 */
7733 			gtk_progress_set_format_string(GTK_PROGRESS(widget), _(v));
7734 			gtk_progress_set_show_text(GTK_PROGRESS(widget), TRUE);
7735 #endif
7736 // !!! Padding = 0
7737 			break;
7738 		/* Add a color patch renderer */
7739 		case op_COLORPATCH:
7740 			widget = gtk_drawing_area_new();
7741 #if GTK_MAJOR_VERSION == 3
7742 			gtk_widget_set_size_request(widget,
7743 				(int)pp[2] >> 16, (int)pp[2] & 0xFFFF);
7744 			g_signal_connect(G_OBJECT(widget), "draw",
7745 				G_CALLBACK(col_expose), v);
7746 #else /* if GTK_MAJOR_VERSION <= 2 */
7747 			gtk_drawing_area_size(GTK_DRAWING_AREA(widget),
7748 				(int)pp[2] >> 16, (int)pp[2] & 0xFFFF);
7749 			gtk_signal_connect(GTK_OBJECT(widget), "expose_event",
7750 				GTK_SIGNAL_FUNC(col_expose), v);
7751 #endif
7752 // !!! Padding = 0
7753 			break;
7754 		/* Add an RGB renderer */
7755 		case op_RGBIMAGE:
7756 			widget = rgbimage(r, (int *)((char *)ddata + (int)pp[2]));
7757 // !!! Padding = 0
7758 			break;
7759 		/* Add a buffered (by pixmap) RGB renderer */
7760 		case op_RGBIMAGEP:
7761 			widget = rgbimagep(r, (int)pp[2] >> 16, (int)pp[2] & 0xFFFF);
7762 // !!! Padding = 0
7763 			break;
7764 		/* Add a framed canvas-based renderer */
7765 		case op_CANVASIMG:
7766 			widget = canvasimg(r, (int)pp[2] >> 16, (int)pp[2] & 0xFFFF, 0);
7767 			if ((CT_WHAT(wp) == ct_SCROLL) && (pk <= pk_DEF))
7768 				pk = pk_BIN;
7769 // !!! Padding = 0
7770 			pk |= pkf_PARENT | pkf_CANVAS;
7771 			break;
7772 		/* Add a framed canvas-based renderer with background */
7773 		case op_CANVASIMGB:
7774 		{
7775 			int *xp = (int *)((char *)ddata + (int)pp[2]);
7776 			widget = canvasimg(r, xp[0], xp[1], xp[2]);
7777 			if ((CT_WHAT(wp) == ct_SCROLL) && (pk <= pk_DEF))
7778 				pk = pk_BIN;
7779 // !!! Padding = 0
7780 			pk |= pkf_PARENT | pkf_CANVAS;
7781 			break;
7782 		}
7783 		/* Add a canvas widget */
7784 		case op_CANVAS:
7785 		{
7786 			GtkWidget *frame;
7787 
7788 			widget = wjcanvas_new();
7789 			wjcanvas_size(widget, (int)v >> 16, (int)v & 0xFFFF);
7790 			wjcanvas_set_expose(widget, GTK_SIGNAL_FUNC(expose_canvas_),
7791 				NEXT_SLOT(r));
7792 
7793 			frame = wjframe_new();
7794 			gtk_widget_show(frame);
7795 			gtk_container_add(GTK_CONTAINER(frame), widget);
7796 
7797 // !!! For now, connection to scrollbars is automatic
7798 			if ((CT_WHAT(wp) == ct_SCROLL) && (pk <= pk_DEF))
7799 				pk = pk_BIN;
7800 			pk |= pkf_PARENT | pkf_CANVAS;
7801 			break;
7802 		}
7803 		/* Add a focusable buffered RGB renderer with cursor */
7804 		case op_FCIMAGEP:
7805 			widget = fcimagep(r, ddata);
7806 // !!! Padding = 0
7807 			break;
7808 		/* Add a non-spinning spin to table slot */
7809 		case op_NOSPIN:
7810 		{
7811 			int n = *(int *)v;
7812 			widget = add_a_spin(n, n, n);
7813 #if GTK_MAJOR_VERSION == 3
7814 			gtk_widget_set_can_focus(widget, FALSE);
7815 #else
7816 			GTK_WIDGET_UNSET_FLAGS(widget, GTK_CAN_FOCUS);
7817 #endif
7818 			tpad = GET_BORDER(SPIN);
7819 			break;
7820 		}
7821 		/* Add a spin, fill from field/var */
7822 		case op_SPIN: case op_SPINc:
7823 			widget = add_a_spin(*(int *)v, (int)pp[2], (int)pp[3]);
7824 #if (GTK_MAJOR_VERSION == 3) || (GTK2VERSION >= 4) /* GTK+ 2.4+ */
7825 			if (op == op_SPINc) gtk_entry_set_alignment(
7826 				GTK_ENTRY(&(GTK_SPIN_BUTTON(widget)->entry)), 0.5);
7827 #endif
7828 			tpad = GET_BORDER(SPIN);
7829 			break;
7830 		/* Add float spin, fill from field/var */
7831 		case op_FSPIN:
7832 			widget = add_float_spin(*(int *)v / 100.0,
7833 				(int)pp[2] / 100.0, (int)pp[3] / 100.0);
7834 			tpad = GET_BORDER(SPIN);
7835 			break;
7836 		/* Add a spin, fill from array */
7837 		case op_SPINa:
7838 		{
7839 			int *xp = v;
7840 			widget = add_a_spin(xp[0], xp[1], xp[2]);
7841 			tpad = GET_BORDER(SPIN);
7842 			break;
7843 		}
7844 		/* Add a grid of spins, fill from array of arrays */
7845 		// !!! Presents one widget out of all grid (the last one)
7846 		case op_TLSPINPACK:
7847 			r[2] = dtail -= VVS(TLSPINPACK_SIZE(pp));
7848 			widget = tlspinpack(r, dtail, CT_TOP(wp), (int)pp[lp + 1]);
7849 			pk = 0;
7850 			break;
7851 		/* Add a spinslider */
7852 		case op_SPINSLIDE: case op_SPINSLIDEa:
7853 		{
7854 			int z = lp > 3 ? (int)pp[4] : 0;
7855 			widget = mt_spinslide_new(z > 0xFFFF ? z >> 16 : -1,
7856 				z & 0xFFFF ? z & 0xFFFF : -1);
7857 			if (op == op_SPINSLIDEa) spin_set_range(widget,
7858 				((int *)v)[1], ((int *)v)[2]);
7859 			else spin_set_range(widget, (int)pp[2], (int)pp[3]);
7860 			gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *(int *)v);
7861 			tpad = GET_BORDER(SPINSLIDE);
7862 #if GTK_MAJOR_VERSION == 1
7863 			have_sliders = TRUE;
7864 #endif
7865 			pk |= pkf_PARENT;
7866 			break;
7867 		}
7868 		/* Add a named checkbox, fill from field/var/inifile */
7869 		case op_CHECKb:
7870 			*(int *)v = inifile_get_gboolean(pp[3], *(int *)v);
7871 			// Fallthrough
7872 		case op_CHECK:
7873 			widget = gtk_check_button_new_with_label(_(wid = pp[2]));
7874 			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
7875 				*(int *)v);
7876 // !!! Padding = 0
7877 			cw = GET_BORDER(CHECK);
7878 			break;
7879 		/* Add a pack of radio buttons for field/var */
7880 		case op_RPACK: case op_RPACKD:
7881 		/* Add a combobox for field/var */
7882 		case op_COMBO:
7883 			widget = mkpack(op != op_COMBO, op == op_RPACKD,
7884 				ref, ddata, r);
7885 // !!! Padding = 0
7886 			cw = GET_BORDER(RPACK);
7887 			if (op != op_COMBO) break;
7888 			cw = GET_BORDER(OPT);
7889 #if GTK2VERSION >= 4 /* GTK+ 2.4+ */
7890 			/* !!! GtkComboBox ignores its border setting, and is
7891 			 * easier to wrap than fix */
7892 			if (cw)
7893 			{
7894 				GtkWidget *w = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
7895 				gtk_container_add(GTK_CONTAINER(w), widget);
7896 				gtk_widget_show(w);
7897 				pk |= pkf_PARENT;
7898 			}
7899 #endif
7900 			break;
7901 		/* Add an option menu for field/var */
7902 		case op_OPT: case op_OPTD:
7903 #if GTK_MAJOR_VERSION == 3
7904 			widget = r[0] = gtk_combo_box_text_new(); // Fix up slot
7905 #else /* if GTK_MAJOR_VERSION <= 2 */
7906 			widget = r[0] = gtk_option_menu_new(); // Fix up slot
7907 			 /* !!! Show now - or size won't be set properly */
7908 			gtk_widget_show(widget);
7909 #endif
7910 #if GTK_MAJOR_VERSION == 2
7911 			gtk_signal_connect(GTK_OBJECT(widget), "realize",
7912 				GTK_SIGNAL_FUNC(opt_size_fix), NULL);
7913 #endif
7914 			opt_reset(r, ddata, *(int *)v);
7915 			/* !!! Border is better left alone - to avoid a drawing
7916 			 * glitch in GTK+1 */
7917 			tpad = GET_BORDER(OPT);
7918 			break;
7919 		/* Add an entry widget, fill from drop-away buffer */
7920 		case op_ENTRY: case op_MLENTRY:
7921 			widget = gtk_entry_new();
7922 			if (lp > 1) gtk_entry_set_max_length(GTK_ENTRY(widget),
7923 				(int)pp[2]);
7924 			if (*(char **)v)
7925 				gtk_entry_set_text(GTK_ENTRY(widget), *(char **)v);
7926 			if (op == op_MLENTRY) accept_ctrl_enter(widget);
7927 			// Replace transient buffer - it may get freed on return
7928 			*(const char **)v = gtk_entry_get_text(GTK_ENTRY(widget));
7929 			tpad = GET_BORDER(ENTRY);
7930 			break;
7931 		/* Add a path entry or box to table */
7932 		case op_PENTRY:
7933 #if GTK_MAJOR_VERSION == 3
7934 			widget = gtk_entry_new();
7935 			gtk_entry_set_max_length(GTK_ENTRY(widget), (int)pp[2]);
7936 #else
7937 			widget = gtk_entry_new_with_max_length((int)pp[2]);
7938 #endif
7939 			set_path(widget, v, PATH_VALUE);
7940 			tpad = GET_BORDER(ENTRY);
7941 			break;
7942 		/* Add a path box to table */
7943 		case op_PATHs:
7944 			v = inifile_get(v, ""); // read and fallthrough
7945 		case op_PATH:
7946 			widget = pathbox(r, GET_BORDER(PATH));
7947 			set_path(widget, v, PATH_VALUE);
7948 // !!! Padding = 0 Border = 0
7949 			pk |= pkf_PARENT;
7950 			break;
7951 		/* Add a text widget, fill from drop-away buffer at pointer */
7952 		case op_TEXT:
7953 			widget = textarea(*(char **)v);
7954 			*(char **)v = NULL;
7955 // !!! Padding = 0 Border = 0
7956 			pk |= pkf_PARENT; // wrapped
7957 			break;
7958 		/* Add a font selector, fill from array: font name/test string */
7959 		case op_FONTSEL:
7960 			widget = fontsel(r, v);
7961 // !!! Border = 4
7962 			cw = 4;
7963 			break;
7964 #ifdef U_CPICK_MTPAINT
7965 		/* Add a hex color entry */
7966 		case op_HEXENTRY:
7967 			widget = hexentry(*(int *)v, r);
7968 // !!! Padding = 0 Border = 0
7969 			break;
7970 		/* Add an eyedropper button */
7971 		case op_EYEDROPPER:
7972 			widget = eyedropper(r);
7973 // !!! Padding = 2 Border = 0
7974 			tpad = 2;
7975 			if (pk == pk_TABLE) pk = pk_TABLEp;
7976 			break;
7977 #endif
7978 		/* Add a togglebutton for selecting shortcut keys */
7979 		case op_KEYBUTTON:
7980 		{
7981 			keybutton_data *dt = (void *)dtail;
7982 			dt->section = ini_setsection(&main_ini, 0, SEC_KEYNAMES);
7983 			ini_transient(&main_ini, dt->section, NULL);
7984 
7985 			widget = gtk_toggle_button_new_with_label(_("New key ..."));
7986 #if GTK_MAJOR_VERSION == 1
7987 			gtk_widget_add_events(widget, GDK_KEY_RELEASE_MASK);
7988 #endif
7989 			gtk_signal_connect(GTK_OBJECT(widget), "key_press_event",
7990 				GTK_SIGNAL_FUNC(convert_key), dt);
7991 			gtk_signal_connect(GTK_OBJECT(widget), "key_release_event",
7992 				GTK_SIGNAL_FUNC(convert_key), dt);
7993 			cw = GET_BORDER(BUTTON);
7994 			break;
7995 		}
7996 		/* Add a button for tablet config dialog */
7997 		case op_TABLETBTN:
7998 			widget = gtk_button_new_with_label(_(v));
7999 			gtk_signal_connect_object(GTK_OBJECT(widget), "clicked",
8000 				GTK_SIGNAL_FUNC(conf_tablet), (gpointer)r);
8001 			cw = GET_BORDER(BUTTON);
8002 			break;
8003 		/* Add a combo-entry for text strings */
8004 		case op_COMBOENTRY:
8005 			widget = comboentry(ddata, r);
8006 // !!! Padding = 5
8007 			tpad = 5;
8008 			break;
8009 		/* Add a color picker box, w/field array, & leave unfilled (?) */
8010 		case op_COLOR: case op_TCOLOR:
8011 			widget = cpick_create(op == op_TCOLOR);
8012 			vdata->fupslot = r; // "Hex" needs defocus to update
8013 // !!! Padding = 0
8014 			break;
8015 		/* Add a colorlist box, fill from fields */
8016 		case op_COLORLIST: case op_COLORLISTN:
8017 			widget = colorlist(r, ddata);
8018 			if ((CT_WHAT(wp) == ct_SCROLL) && (pk <= pk_DEF))
8019 #ifdef U_LISTS_GTK1
8020 				pk = pk_SCROLLVPm;
8021 #else
8022 				pk = pk_BIN; // auto-connects to scrollbars
8023 #endif
8024 			break;
8025 		/* Add a buttonbar for gradient */
8026 		case op_GRADBAR:
8027 			widget = gradbar(r, ddata);
8028 // !!! Padding = 0
8029 			break;
8030 		/* Add a combo for percent values */
8031 		case op_PCTCOMBO:
8032 			widget = pctcombo(r);
8033 // !!! Padding = 0
8034 			break;
8035 		/* Add a list with pre-defined columns */
8036 		case op_LISTCCr:
8037 			widget = listcc(r, ddata, &c);
8038 			/* !!! For GtkTreeView this does not do anything in GTK+2
8039 			 * and produces background-colored border in GTK+3;
8040 			 * need to fix the coloring OR ignore the border: list in
8041 			 * "Configure Animation" looks good without it too - WJ */
8042 			cw = GET_BORDER(LISTCC);
8043 			if ((CT_WHAT(wp) == ct_SCROLL) && (pk <= pk_DEF))
8044 #ifdef U_LISTS_GTK1
8045 				pk = pk_SCROLLVPv;
8046 #else
8047 				pk = pk_BIN; // auto-connects to scrollbars
8048 #endif
8049 			break;
8050 		/* Add a clist with pre-defined columns */
8051 		case op_LISTC: case op_LISTCd: case op_LISTCu:
8052 		case op_LISTCS: case op_LISTCX:
8053 			widget = listc(r, ddata, &c);
8054 // !!! Border = 0
8055 			if ((CT_WHAT(wp) == ct_SCROLL) && (pk <= pk_DEF))
8056 				pk = pk_BIN; // auto-connects to scrollbars
8057 			break;
8058 		/* Add a clickable button */
8059 		case op_BUTTON:
8060 			wid = (int)pp[0] & WB_SFLAG ? v : "="; // Hide by default
8061 			// Fallthrough
8062 		case op_OKBTN: case op_CANCELBTN: case op_DONEBTN:
8063 		{
8064 			widget = gtk_button_new_with_label(_(v));
8065 			if ((op == op_OKBTN) || (op == op_DONEBTN))
8066 			{
8067 				gtk_widget_add_accelerator(widget, "clicked", ag,
8068 					KEY(Return), 0, (GtkAccelFlags)0);
8069 				gtk_widget_add_accelerator(widget, "clicked", ag,
8070 					KEY(KP_Enter), 0, (GtkAccelFlags)0);
8071 				accel |= 1;
8072 			}
8073 			if ((op == op_CANCELBTN) || (op == op_DONEBTN))
8074 			{
8075 				gtk_widget_add_accelerator(widget, "clicked", ag,
8076 					KEY(Escape), 0, (GtkAccelFlags)0);
8077 				add_del(NEXT_SLOT(r), window);
8078 				accel |= 1;
8079 			}
8080 			/* Click-event */
8081 			if ((op != op_BUTTON) || pp[3])
8082 				gtk_signal_connect_object(GTK_OBJECT(widget),
8083 					"clicked", GTK_SIGNAL_FUNC(do_evt_1_d),
8084 					(gpointer)NEXT_SLOT(r));
8085 			cw = GET_BORDER(BUTTON);
8086 			break;
8087 		}
8088 		/* Add a toggle button to OK-box */
8089 		case op_TOGGLE:
8090 			widget = gtk_toggle_button_new_with_label(_(pp[2]));
8091 			if (pp[4]) gtk_signal_connect(GTK_OBJECT(widget),
8092 				"toggled", GTK_SIGNAL_FUNC(get_evt_1), NEXT_SLOT(r));
8093 			cw = GET_BORDER(BUTTON);
8094 			break;
8095 		/* Add a toolbar */
8096 		case op_TOOLBAR: case op_SMARTTBAR:
8097 		{
8098 			GtkWidget *bar;
8099 #if GTK_MAJOR_VERSION <= 2
8100 			GtkWidget *vport;
8101 			smarttbar_data *sd;
8102 #endif
8103 
8104 			tbar = r;
8105 			rvar = rslot = NULL;
8106 #if GTK_MAJOR_VERSION == 1
8107 			widget = gtk_toolbar_new(GTK_ORIENTATION_HORIZONTAL,
8108 				GTK_TOOLBAR_ICONS);
8109 #else /* #if GTK_MAJOR_VERSION >= 2 */
8110 			widget = gtk_toolbar_new();
8111 			gtk_toolbar_set_style(GTK_TOOLBAR(widget), GTK_TOOLBAR_ICONS);
8112 #endif
8113 			ct = ct_TBAR;
8114 			if (op != op_SMARTTBAR)
8115 			{
8116 				tpad = GET_BORDER(TOOLBAR);
8117 				break;
8118 			}
8119 
8120 			// !!! Toolbar is what sits on stack till SMARTTBMORE
8121 			sw = bar = widget;
8122 			gtk_widget_show(bar);
8123 
8124 #if GTK_MAJOR_VERSION == 3
8125 			/* Just add a box, to hold extra things at end */
8126 			widget = hbox_new(0);
8127 			pack(widget, bar);
8128 #else /* #if GTK_MAJOR_VERSION <= 2 */
8129 			widget = wj_size_box();
8130 
8131 			/* Make datastruct */
8132 			sd = (void *)dtail;
8133 			sd->tbar = bar;
8134 			sd->r = r;
8135 
8136 			gtk_signal_connect(GTK_OBJECT(widget), "size_request",
8137 				GTK_SIGNAL_FUNC(htoolbox_size_req), sd);
8138 			gtk_signal_connect(GTK_OBJECT(widget), "size_allocate",
8139 				GTK_SIGNAL_FUNC(htoolbox_size_alloc), sd);
8140 
8141 			vport = pack(widget, gtk_viewport_new(NULL, NULL));
8142 			gtk_viewport_set_shadow_type(GTK_VIEWPORT(vport),
8143 				GTK_SHADOW_NONE);
8144 			gtk_widget_show(vport);
8145 			vport_noshadow_fix(vport);
8146 			sd->vport = vport;
8147 			gtk_container_add(GTK_CONTAINER(vport), bar);
8148 #endif /* GTK+1&2 */
8149 // !!! Padding = 0
8150 			break;
8151 		}
8152 		/* Add the arrow button to smart toolbar */
8153 		case op_SMARTTBMORE:
8154 		{
8155 			GtkWidget *box = tbar[0];
8156 
8157 			// !!! Box replaces toolbar on stack
8158 			CT_POP(wp);
8159 			CT_PUSH(wp, box, ct_BOX);
8160 #if GTK_MAJOR_VERSION == 3
8161 			/* Button is builtin */
8162 			widget = NULL;
8163 			pk = pk_NONE;
8164 #else /* #if GTK_MAJOR_VERSION <= 2 */
8165 			{
8166 				smarttbar_data *sd = tbar[2];
8167 				sd->r2 = r; // remember where the slots end
8168 
8169 				widget = smarttbar_button(sd, v);
8170 				pk = pk_SHOW;
8171 			}
8172 #endif /* GTK+1&2 */
8173 			break;
8174 		}
8175 		/* Add a container-toggle beside toolbar */
8176 		case op_TBBOXTOG:
8177 			widget = pack(CT_N(wp, 1), gtk_toggle_button_new());
8178 #if GTK_MAJOR_VERSION == 3
8179 			gtk_widget_set_tooltip_text(widget, _(wid = pp[3]));
8180 #else
8181 			/* Parasite tooltip on toolbar */
8182 			gtk_tooltips_set_tip(GTK_TOOLBAR(CT_TOP(wp))->tooltips,
8183 				widget, _(wid = pp[3]), "Private");
8184 #endif
8185 #if (GTK_MAJOR_VERSION == 3) || (GTK2VERSION >= 4) /* GTK+ 2.4+ */
8186 			gtk_button_set_focus_on_click(GTK_BUTTON(widget), FALSE);
8187 #endif
8188 			gtk_signal_connect(GTK_OBJECT(widget), "clicked",
8189 				GTK_SIGNAL_FUNC(toolbar_lclick), NEXT_SLOT(tbar));
8190 			ct = ct_BIN;
8191 			pk = pk_SHOW;
8192 			// Fallthrough
8193 		/* Add a toolbar button/toggle */
8194 		case op_TBBUTTON: case op_TBTOGGLE: case op_TBRBUTTON:
8195 		{
8196 			GtkWidget *rb = NULL;
8197 #if GTK_MAJOR_VERSION == 3
8198 			GtkToolItem *it = NULL;
8199 			GtkWidget *m;
8200 #endif
8201 
8202 			if (keygroup) keymap_add(keymap, r, pp[3], keygroup);
8203 
8204 			r[2] = tbar; // link to toolbar slot
8205 			if (op == op_TBRBUTTON)
8206 			{
8207 				static int set = TRUE;
8208 
8209 				if (rvar == v) rb = rslot[0];
8210 				/* Now this represents group */
8211 				rslot = r; rvar = v;
8212 				/* Activate the one button whose ID matches var */
8213 				v = *(int *)v == (int)pp[2] ? &set : NULL;
8214 			}
8215 
8216 #if GTK_MAJOR_VERSION == 3
8217 			if (op == op_TBRBUTTON)
8218 				it = gtk_radio_tool_button_new_from_widget(
8219 					GTK_RADIO_TOOL_BUTTON(rb));
8220 			else if (op == op_TBTOGGLE)
8221 				it = gtk_toggle_tool_button_new();
8222 			else if (op == op_TBBUTTON)
8223 				it = gtk_tool_button_new(NULL, NULL);
8224 			if (it)
8225 			{
8226 				gtk_tool_item_set_tooltip_text(it, _(wid = pp[3]));
8227 				gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(it),
8228 					xpm_image(pp[4]));
8229 				g_signal_connect(G_OBJECT(it),
8230 					op == op_TBBUTTON ? "clicked" : "toggled",
8231 					G_CALLBACK(toolbar_lclick), NEXT_SLOT(tbar));
8232 				gtk_toolbar_insert(GTK_TOOLBAR(CT_TOP(wp)), it, -1);
8233 				widget = GTK_WIDGET(it);
8234 				pk = pk_SHOW;
8235 				/* Set overflow menu to usable state */
8236 				if (GET_OP(tbar) == op_SMARTTBAR)
8237 				{
8238 					m = gtk_tool_item_retrieve_proxy_menu_item(it);
8239 					gtk_container_remove(GTK_CONTAINER(m),
8240 						gtk_bin_get_child(GTK_BIN(m)));
8241 					gtk_container_add(GTK_CONTAINER(m),
8242 						xpm_image(pp[4]));
8243 					gtk_widget_set_tooltip_text(m, _(pp[3]));
8244 					/* Prevent item's re-creation */
8245 					g_signal_connect(G_OBJECT(it),
8246 						"create_menu_proxy",
8247 						G_CALLBACK(leave_be), NULL);
8248 				}
8249 				if (v) gtk_toggle_tool_button_set_active(
8250 					GTK_TOGGLE_TOOL_BUTTON(widget), *(int *)v);
8251 			}
8252 			else if (v) gtk_toggle_button_set_active(
8253 				GTK_TOGGLE_BUTTON(widget), *(int *)v);
8254 			g_object_set_qdata(G_OBJECT(widget), tool_key, r);
8255 #else /* #if GTK_MAJOR_VERSION <= 2 */
8256 			if (op != op_TBBOXTOG) widget = gtk_toolbar_append_element(
8257 				GTK_TOOLBAR(CT_TOP(wp)),
8258 				(op == op_TBBUTTON ? GTK_TOOLBAR_CHILD_BUTTON :
8259 				op == op_TBRBUTTON ? GTK_TOOLBAR_CHILD_RADIOBUTTON :
8260 				GTK_TOOLBAR_CHILD_TOGGLEBUTTON), rb,
8261 				NULL, _(wid = pp[3]), "Private", xpm_image(pp[4]),
8262 				GTK_SIGNAL_FUNC(toolbar_lclick), NEXT_SLOT(tbar));
8263 			if (v) gtk_toggle_button_set_active(
8264 				GTK_TOGGLE_BUTTON(widget), *(int *)v);
8265 			gtk_object_set_user_data(GTK_OBJECT(widget), r);
8266 #endif /* GTK+1&2 */
8267 			if (lp > 4) gtk_signal_connect(GTK_OBJECT(widget),
8268 				"button_press_event",
8269 				GTK_SIGNAL_FUNC(toolbar_rclick), SLOT_N(tbar, 2));
8270 			break;
8271 		}
8272 		/* Add a toolbar separator */
8273 		case op_TBSPACE:
8274 		{
8275 #if GTK_MAJOR_VERSION == 3
8276 			GtkToolItem *it = gtk_separator_tool_item_new();
8277 			gtk_widget_show(GTK_WIDGET(it));
8278 			gtk_toolbar_insert(GTK_TOOLBAR(CT_TOP(wp)), it, -1);
8279 #else /* #if GTK_MAJOR_VERSION <= 2 */
8280 			gtk_toolbar_append_space(GTK_TOOLBAR(CT_TOP(wp)));
8281 #endif
8282 			break;
8283 		}
8284 		/* Add a two/one row container for 2 toolbars */
8285 		case op_TWOBOX:
8286 #if GTK_MAJOR_VERSION == 3
8287 			widget = gtk_flow_box_new();
8288 			gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(widget),
8289 				GTK_SELECTION_NONE);
8290 			ct = ct_CONT;
8291 #else /* #if GTK_MAJOR_VERSION <= 2 */
8292 			widget = wj_size_box();
8293 			gtk_signal_connect(GTK_OBJECT(widget), "size_request",
8294 				GTK_SIGNAL_FUNC(twobar_size_req), NULL);
8295 			gtk_signal_connect(GTK_OBJECT(widget), "size_allocate",
8296 				GTK_SIGNAL_FUNC(twobar_size_alloc), NULL);
8297 			ct = ct_BOX;
8298 #endif
8299 // !!! Padding = 0
8300 			break;
8301 		/* Add a menubar */
8302 		case op_MENUBAR: case op_SMARTMENU:
8303 		{
8304 			GtkWidget *bar;
8305 			smartmenu_data *sd;
8306 
8307 			// Stop dynamic allocation of accelerators during runtime
8308 			accel |= 3;
8309 
8310 			tbar = r;
8311 			rvar = rslot = NULL;
8312 			widget = gtk_menu_bar_new();
8313 
8314 			ct = ct_CONT;
8315 // !!! Padding = 0
8316 			if (op != op_SMARTMENU) break;
8317 
8318 			ct = ct_SMARTMENU;
8319 			// !!! Menubar is what sits on stack
8320 			sw = bar = widget;
8321 			gtk_widget_show(bar);
8322 
8323 			/* Make datastruct */
8324 			sd = (void *)dtail;
8325 			sd->mbar = bar;
8326 			sd->r = r;
8327 
8328 #if GTK_MAJOR_VERSION <= 2
8329 			widget = wj_size_box();
8330 
8331 			gtk_signal_connect(GTK_OBJECT(widget), "size_request",
8332 				GTK_SIGNAL_FUNC(smart_menu_size_req), sd);
8333 			gtk_signal_connect(GTK_OBJECT(widget), "size_allocate",
8334 				GTK_SIGNAL_FUNC(smart_menu_size_alloc), sd);
8335 
8336 			pack(widget, bar);
8337 #endif
8338 			break;
8339 		}
8340 		/* Add a dropdown submenu */
8341 		case op_SUBMENU: case op_ESUBMENU: case op_SSUBMENU:
8342 		{
8343 			GtkWidget *label, *menu;
8344 			char *s;
8345 #if GTK_MAJOR_VERSION <= 2
8346 			guint keyval;
8347 #endif
8348 			int l;
8349 
8350 			gid = v; /* For keymap */
8351 
8352 			widget = gtk_menu_item_new_with_label("");
8353 			if (op == op_ESUBMENU)
8354 				gtk_menu_item_right_justify(GTK_MENU_ITEM(widget));
8355 
8356 			l = strspn(s = v, "/");
8357 			if (s[l]) s = _(s); // Translate
8358 			s += l;
8359 
8360 			label = gtk_bin_get_child(GTK_BIN(widget));
8361 #if GTK_MAJOR_VERSION == 3
8362 			gtk_label_set_text_with_mnemonic(GTK_LABEL(label), s);
8363 			/* !!! In case any non-toplevel submenu has an underline,
8364 			 * in GTK+3 it'll have to be cut out from string beforehand */
8365 #else /* #if GTK_MAJOR_VERSION <= 2 */
8366 			keyval = gtk_label_parse_uline(GTK_LABEL(label), s);
8367 			/* Toplevel submenus can have Alt+letter shortcuts */
8368 			if ((l < 2) && (keyval != GDK_VoidSymbol))
8369 #if GTK_MAJOR_VERSION == 1
8370 				gtk_widget_add_accelerator(widget, "activate_item",
8371 					ag, keyval, GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
8372 #else
8373 				gtk_label_set_text_with_mnemonic(GTK_LABEL(label), s);
8374 #endif
8375 #endif /* GTK+1&2 */
8376 
8377 			sw = menu = gtk_menu_new();
8378 			ct = ct_CONT;
8379 			gtk_menu_set_accel_group(GTK_MENU(menu), ag);
8380 			gtk_menu_item_set_submenu(GTK_MENU_ITEM(widget), menu);
8381 
8382 			break;
8383 		}
8384 		/* Add a menu item/toggle */
8385 		case op_MENUITEM: case op_MENUCHECK: case op_MENURITEM:
8386 		{
8387 			char *s;
8388 			int l;
8389 
8390 			if (keygroup) keymap_add(keymap, r, pp[3], keygroup);
8391 
8392 			r[2] = tbar; // link to menu slot
8393 			if (op == op_MENUCHECK)
8394 				widget = gtk_check_menu_item_new_with_label("");
8395 			else if (op == op_MENURITEM)
8396 			{
8397 				GSList *group = NULL;
8398 
8399 				if (rvar == v) group = gtk_radio_menu_item_group(
8400 					rslot[0]);
8401 				/* Now this represents group */
8402 				rslot = r; rvar = v;
8403 				widget = gtk_radio_menu_item_new_with_label(group, "");
8404 			}
8405 #if GTK_MAJOR_VERSION >= 2
8406 			else if ((lp > 3) && show_menu_icons)
8407 			{
8408 				widget = gtk_image_menu_item_new_with_label("");
8409 				gtk_image_menu_item_set_image(
8410 					GTK_IMAGE_MENU_ITEM(widget), xpm_image(pp[4]));
8411 			}
8412 #endif
8413 			else widget = gtk_menu_item_new_with_label("");
8414 
8415 			if (v) /* Initialize a check/radio item */
8416 			{
8417 				int f = *(int *)v;
8418 				/* Activate the one button whose ID matches var */
8419 				if ((op != op_MENURITEM) || (f = f == (int)pp[2]))
8420 					gtk_check_menu_item_set_active(
8421 						GTK_CHECK_MENU_ITEM(widget), f);
8422 #if GTK_MAJOR_VERSION <= 2
8423 				gtk_check_menu_item_set_show_toggle(
8424 					GTK_CHECK_MENU_ITEM(widget), TRUE);
8425 #endif
8426 			}
8427 
8428 			l = strspn(s = pp[3], "/");
8429 			if (s[l]) s = _(s); // Translate
8430 			s += l;
8431 
8432 #if GTK_MAJOR_VERSION == 3
8433 			/* !!! In case any regular menuitem has an underline,
8434 			 * in GTK+3 it'll have to be cut out from string beforehand */
8435 			gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(widget))), s);
8436 
8437 			g_object_set_qdata(G_OBJECT(widget), tool_key, r);
8438 #else /* #if GTK_MAJOR_VERSION <= 2 */
8439 			gtk_label_parse_uline(GTK_LABEL(GTK_BIN(widget)->child), s);
8440 
8441 			gtk_object_set_user_data(GTK_OBJECT(widget), r);
8442 #endif
8443 			gtk_signal_connect(GTK_OBJECT(widget), "activate",
8444 				GTK_SIGNAL_FUNC(menu_evt), NEXT_SLOT(tbar));
8445 
8446 #if GTK_MAJOR_VERSION >= 2
8447 		/* !!! Otherwise GTK+ won't add spacing to an empty accel field */
8448 			gtk_widget_set_accel_path(widget, FAKE_ACCEL, ag);
8449 #endif
8450 #if (GTK_MAJOR_VERSION == 3) || (GTK2VERSION >= 4)
8451 		/* !!! GTK+ 2.4+ ignores invisible menu items' keys by default */
8452 			gtk_signal_connect(GTK_OBJECT(widget), "can_activate_accel",
8453 				GTK_SIGNAL_FUNC(menu_allow_key), NULL);
8454 #endif
8455 			break;
8456 		}
8457 		/* Add a tearoff menu item */
8458 		case op_MENUTEAR:
8459 			widget = gtk_tearoff_menu_item_new();
8460 			break;
8461 		/* Add a separator menu item */
8462 		case op_MENUSEP:
8463 			widget = gtk_menu_item_new();
8464 			gtk_widget_set_sensitive(widget, FALSE);
8465 			break;
8466 		/* Add a mount socket with custom-built separable widget */
8467 		case op_MOUNT:
8468 		{
8469 			void **what = r[2] = ((mnt_fn)pp[2])(res);
8470 			*(int *)v = TRUE;
8471 			widget = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
8472 			gtk_container_add(GTK_CONTAINER(widget),
8473 				GET_REAL_WINDOW(what));
8474 // !!! Padding = 0
8475 			if (lp > 2 + 2) // put socket in pane w/preset size
8476 			{
8477 				GtkWidget *pane = gtk_vpaned_new();
8478 				paned_mouse_fix(pane);
8479 				gtk_paned_set_position(GTK_PANED(pane),
8480 					inifile_get_gint32(pp[3], (int)pp[4]));
8481 				gtk_paned_pack2(GTK_PANED(pane), vbox_new(0), TRUE, TRUE);
8482 				gtk_widget_show_all(pane);
8483 				gtk_paned_pack1(GTK_PANED(pane),
8484 					widget, FALSE, TRUE);
8485 				pk |= pkf_PARENT;
8486 			}
8487 			break;
8488 		}
8489 		/* Steal a widget from its mount socket by slot ref */
8490 		case op_REMOUNT:
8491 		{
8492 			void **where = *(void ***)v;
8493 			GtkWidget *what = gtk_bin_get_child(GTK_BIN(where[0]));
8494 
8495 			widget = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
8496 			gtk_widget_hide(where[0]);
8497 			gtk_widget_reparent(what, widget);
8498 			get_evt_1(where[0], NEXT_SLOT(where));
8499 			break;
8500 		}
8501 		/* Add a height-limiter item */
8502 		case op_HEIGHTBAR:
8503 		{
8504 			widget = gtk_label_new(""); // Gives useful lower limit
8505 #if GTK_MAJOR_VERSION == 3
8506 			GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);
8507 			gtk_size_group_set_ignore_hidden(sg, FALSE);
8508 			CT_PUSH(wp, sg, ct_SGROUP);
8509 #else /* #if GTK_MAJOR_VERSION <= 2 */
8510 			gtk_signal_connect(GTK_OBJECT(widget), "size_request",
8511 				GTK_SIGNAL_FUNC(heightbar_size_req), NULL);
8512 #endif
8513 			break;
8514 		}
8515 #if 0
8516 		/* Call a function */
8517 		case op_EXEC:
8518 			r = ((ext_fn)v)(r, &wp, res);
8519 			continue;
8520 #endif
8521 		/* Call a V-code subroutine */
8522 		case op_CALL:
8523 			*rp++ = ifcode;
8524 			// Fallthrough
8525 		/* Do a V-code jump */
8526 		case op_GOTO:
8527 			ifcode = v;
8528 			continue;
8529 		/* Return from V-code subroutine */
8530 		case op_RET:
8531 			ifcode = *--rp;
8532 			continue;
8533 		/* Skip next token(s) if/unless field/var is unset */
8534 		case op_IF: case op_UNLESS:
8535 			if (!*(int *)v ^ (op != op_IF))
8536 				ifcode = skip_if(pp);
8537 			continue;
8538 		/* Skip next token(s) unless inifile var, set by default, is unset */
8539 		case op_UNLESSbt:
8540 			if (inifile_get_gboolean(v, TRUE))
8541 				ifcode = skip_if(pp);
8542 			continue;
8543 		/* Put last referrable widget into activation map */
8544 		case op_ACTMAP:
8545 			if (lp > 1) vdata->vismask = (unsigned)v; // VISMASK
8546 			else
8547 			{
8548 				void **where = vdata->actmap + vdata->actn++ * ACT_SIZE;
8549 				ADD_ACT(where, origin_slot(PREV_SLOT(r)), v);
8550 			}
8551 			continue;
8552 		/* Set up a configurable keymap for widgets */
8553 		case op_KEYMAP:
8554 			vdata->keymap = r;
8555 			r[2] = dtail -= VVS(KEYMAP_SIZE(sz.keys));
8556 			keymap = (void *)dtail;
8557 			keymap->res = v;
8558 			keymap->slotsec = keygroup =
8559 				ini_setsection(&main_ini, 0, pp[2]);
8560 			ini_transient(&main_ini, keygroup, NULL); // Not written out
8561 			widget = NULL;
8562 			break;
8563 		/* Add a shortcut, from text desc, to last referrable widget */
8564 		case op_SHORTCUT:
8565 		{
8566 			void **slot = origin_slot(PREV_SLOT(r));
8567 			int op = GET_OP(slot);
8568 			guint keyval = 0, mods = 0;
8569 
8570 			if (!lp); // Do nothing
8571 			else if (lp < 2) gtk_accelerator_parse(v, &keyval, &mods);
8572 			else keyval = (guint)v , mods = (guint)pp[2];
8573 
8574 			if (keygroup &&
8575 				// Already mapped
8576 				((keymap->slots[keymap->nslots].slot == slot) ||
8577 				// On-demand mapping for script mode menuitems
8578 				((op >= op_uMENU_0) && (op < op_uMENU_LAST) &&
8579 				 keymap_add(keymap, slot, GET_DESCV(slot, 3), keygroup))))
8580 			{
8581 				if (lp) keymap_map(keymap, keymap->nslots, keyval, mods);
8582 				continue;
8583 			}
8584 #if GTK_MAJOR_VERSION >= 2
8585 			/* !!! In case one had been set (for menu spacing) */
8586 			gtk_widget_set_accel_path(*slot, NULL, ag);
8587 #endif
8588 			gtk_widget_add_accelerator(*slot, "activate",
8589 				ag, keyval, mods, GTK_ACCEL_VISIBLE);
8590 			accel |= 1;
8591 			continue;
8592 		}
8593 		/* Install priority key handler for when widget is focused */
8594 		case op_WANTKEYS:
8595 			vdata->wantkey = r;
8596 			widget = NULL;
8597 			break;
8598 		/* Store a reference to whatever is next into field */
8599 		case op_REF:
8600 			*(void **)v = r;
8601 			continue;
8602 		/* Make toplevel window shrinkable */
8603 		case op_MKSHRINK:
8604 #if GTK_MAJOR_VERSION == 3
8605 			/* Just do smarter initial sizing, here it is enough */
8606 			gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
8607 			g_signal_connect(window, "draw", G_CALLBACK(make_resizable), NULL);
8608 #else /* #if GTK_MAJOR_VERSION <= 2 */
8609 			gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
8610 #endif
8611 			continue;
8612 		/* Make toplevel window non-resizable */
8613 		case op_NORESIZE:
8614 #if GTK_MAJOR_VERSION == 3
8615 			gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
8616 #else
8617 			gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, TRUE);
8618 #endif
8619 			continue;
8620 		/* Make scrolled window request max size */
8621 		case op_WANTMAX:
8622 			mods.wantmax = (int)v + 1;
8623 			continue;
8624 		/* Make widget keep max requested width/height */
8625 		case op_KEEPSIZE:
8626 			widget_set_keepsize(widget, lp);
8627 			continue;
8628 		/* Use saved size & position for window */
8629 		case op_WXYWH:
8630 		{
8631 			unsigned int n = (unsigned)pp[2];
8632 			vdata->ininame = v;
8633 			vdata->xywh[2] = n >> 16;
8634 			vdata->xywh[3] = n & 0xFFFF;
8635 			if (v) rw_pos(vdata, FALSE);
8636 			continue;
8637 		}
8638 		/* Make toplevel window be positioned at mouse */
8639 		case op_WPMOUSE: wpos = GTK_WIN_POS_MOUSE; continue;
8640 		/* Make toplevel window be positioned anywhere WM pleases */
8641 		case op_WPWHEREVER: wpos = GTK_WIN_POS_NONE; continue;
8642 		/* Make last referrable widget hidden */
8643 		case op_HIDDEN:
8644 			gtk_widget_hide(get_wrap(origin_slot(PREV_SLOT(r))));
8645 			continue;
8646 		/* Make last referrable widget insensitive */
8647 		case op_INSENS:
8648 			cmd_sensitive(origin_slot(PREV_SLOT(r)), FALSE);
8649 			continue;
8650 		/* Make last referrable widget focused */
8651 		case op_FOCUS:
8652 		{
8653 			void **orig = origin_slot(PREV_SLOT(r));
8654 			/* !!! For GtkFontSelection, focusing needs be done after
8655 			 * window is shown - in GTK+ 2.24.10, at least */
8656 			if (GET_OP(orig) == op_FONTSEL) gtk_signal_connect_object(
8657 				GTK_OBJECT(window), "show",
8658 				GTK_SIGNAL_FUNC(gtk_widget_grab_focus),
8659 				(gpointer)gtk_font_selection_get_preview_entry(
8660 				GTK_FONT_SELECTION(*orig)));
8661 			else gtk_widget_grab_focus(*orig);
8662 			continue;
8663 		}
8664 		/* Set fixed/minimum width for next widget */
8665 		case op_WIDTH:
8666 			mods.minw = (int)v;
8667 			continue;
8668 		/* Set fixed/minimum height for next widget */
8669 		case op_HEIGHT:
8670 			mods.minh = (int)v;
8671 			continue;
8672 		/* Make window transient to given widget-map */
8673 		case op_ONTOP:
8674 			tparent = !v ? NULL :
8675 				GTK_WINDOW(GET_REAL_WINDOW(*(void ***)v));
8676 			continue;
8677 		/* Change identifier, for reusable toplevels */
8678 		case op_IDENT:
8679 			ident = v;
8680 			continue;
8681 		/* Raise window after displaying */
8682 		case op_RAISED:
8683 			vdata->raise = TRUE;
8684 			continue;
8685 		/* Start group of list columns */
8686 		case op_WLIST:
8687 			memset(&c, 0, sizeof(c));
8688 			continue;
8689 		/* Add a datablock pseudo-column */
8690 		case op_COLUMNDATA:
8691 			c.dcolumn = pp;
8692 			continue;
8693 		/* Add a regular list column */
8694 		case op_IDXCOLUMN: case op_TXTCOLUMN: case op_XTXTCOLUMN:
8695 		case op_FILECOLUMN: case op_CHKCOLUMN:
8696 			((swdata *)dtail)->cnt = c.ncol;
8697 			c.columns[c.ncol++] = r;
8698 			pk = pk_UNREAL;
8699 			if ((script || scripted) && (lp - ref * 2 > 1))
8700 			{
8701 				wid = pp[4];
8702 				tpad = 0;
8703 				pk = pk_UNREALV;
8704 			}
8705 			break;
8706 		/* Create an XBM cursor */
8707 		case op_XBMCURSOR:
8708 		{
8709 			int xyl = (int)pp[3], l = xyl >> 16;
8710 			widget = (void *)make_cursor(v, pp[2], l, l,
8711 				(xyl >> 8) & 255, xyl & 255);
8712 			break;
8713 		}
8714 		/* Create a system cursor */
8715 		case op_SYSCURSOR:
8716 			widget = (void *)gdk_cursor_new((int)v);
8717 			break;
8718 		/* Add a group of clipboard formats */
8719 		case op_CLIPFORM:
8720 		{
8721 			int i, n = (int)pp[2], l = sizeof(clipform_data) +
8722 				sizeof(GtkTargetEntry) * (n - 1);
8723 			clipform_data *cd = calloc(1, l);
8724 
8725 			cd->n = n;
8726 			cd->src = v;
8727 			for (i = 0; i < n; i++)
8728 			{
8729 				cd->ent[i].target = cd->src[i].target;
8730 				cd->ent[i].info = i;
8731 				/* cd->ent[i].flags = 0; */
8732 			}
8733 			cd->targets = gtk_target_list_new(cd->ent, n);
8734 
8735 			widget = (void *)cd;
8736 			break;
8737 		}
8738 		/* Install drag/drop handlers */
8739 // !!! For drag, this must be done before mouse event handlers
8740 		case op_DRAGDROP:
8741 			widget = dragdrop(r);
8742 			break;
8743 		/* Add a clipboard control slot */
8744 		case op_CLIPBOARD:
8745 			widget = **(void ***)v; // from CLIPFORM
8746 			break;
8747 		/* Install activate event handler */
8748 		case op_EVT_OK:
8749 		{
8750 			void **slot = origin_slot(PREV_SLOT(r));
8751 			int what = GET_OP(slot);
8752 // !!! Support only what actually used on, and their brethren
8753 			switch (what)
8754 			{
8755 			case op_ENTRY: case op_MLENTRY: case op_PENTRY:
8756 			case op_PATH: case op_PATHs:
8757 				gtk_signal_connect(GTK_OBJECT(*slot), "activate",
8758 					GTK_SIGNAL_FUNC(get_evt_1), r);
8759 				break;
8760 			case op_LISTC: case op_LISTCd: case op_LISTCu:
8761 			case op_LISTCS: case op_LISTCX:
8762 			{
8763 				listc_data *ld = slot[2];
8764 				ld->ok = r;
8765 				break;
8766 			}
8767 			}
8768 			widget = NULL;
8769 			break;
8770 		}
8771 		/* Install destroy event handler (onto window) */
8772 		case op_EVT_CANCEL:
8773 			add_del(r, window);
8774 			widget = NULL;
8775 			break;
8776 		/* Install deallocation event handler */
8777 		case op_EVT_DESTROY:
8778 			vdata->destroy = r;
8779 			widget = NULL;
8780 			break;
8781 		/* Install key event handler */
8782 		case op_EVT_KEY:
8783 		{
8784 			void **slot = origin_slot(PREV_SLOT(r));
8785 			int toplevel = (slot == GET_WINDOW(res)) &&
8786 				GTK_IS_WINDOW(slot[0]);
8787 
8788 			gtk_signal_connect(GTK_OBJECT(*slot), "key_press_event",
8789 				// Special handler for toplevels
8790 				toplevel ? GTK_SIGNAL_FUNC(window_evt_key) :
8791 				GTK_SIGNAL_FUNC(get_evt_key), r);
8792 			enable_events(slot, op);
8793 			widget = NULL;
8794 			break;
8795 		}
8796 		/* Install mouse event handler */
8797 		case op_EVT_MOUSE: case op_EVT_MMOUSE: case op_EVT_RMOUSE:
8798 		case op_EVT_XMOUSE: case op_EVT_MXMOUSE: case op_EVT_RXMOUSE:
8799 			add_mouse(r, op);
8800 			widget = NULL;
8801 			break;
8802 		/* Install crossing event handler */
8803 		case op_EVT_CROSS:
8804 		{
8805 			void **slot = origin_slot(PREV_SLOT(r));
8806 			gtk_signal_connect(GTK_OBJECT(*slot), "enter_notify_event",
8807 				GTK_SIGNAL_FUNC(get_evt_cross), r);
8808 			gtk_signal_connect(GTK_OBJECT(*slot), "leave_notify_event",
8809 				GTK_SIGNAL_FUNC(get_evt_cross), r);
8810 			enable_events(slot, op);
8811 			widget = NULL;
8812 			break;
8813 		}
8814 #if GTK_MAJOR_VERSION >= 2
8815 		/* Install scroll event handler */
8816 		case op_EVT_SCROLL:
8817 		{
8818 			void **slot = origin_slot(PREV_SLOT(r));
8819 			gtk_signal_connect(GTK_OBJECT(*slot), "scroll_event",
8820 				GTK_SIGNAL_FUNC(get_evt_scroll), r);
8821 	/* !!! The classical scroll events cannot be reliably received on any
8822 	 * GdkWindow with GDK_SMOOTH_SCROLL_MASK set (such as the ones of GTK+3
8823 	 * builtin scrollable widgets); if that is ever needed, enable_events()
8824 	 * will need install a realize handler which forcibly removes the flag
8825 	 * from all widget's GdkWindows - WJ */
8826 			enable_events(slot, op);
8827 			widget = NULL;
8828 			break;
8829 		}
8830 #endif
8831 		/* Install Change-event handler */
8832 		case op_EVT_CHANGE:
8833 		{
8834 			void **slot = origin_slot(PREV_SLOT(r));
8835 			int what = GET_OP(slot);
8836 // !!! Support only what actually used on, and their brethren
8837 			switch (what)
8838 			{
8839 			case op_SPINSLIDE: case op_SPINSLIDEa:
8840 			case op_SPIN: case op_SPINc: case op_SPINa:
8841 			case op_FSPIN:
8842 				spin_connect(*slot,
8843 					GTK_SIGNAL_FUNC(get_evt_1), r);
8844 				break;
8845 			case op_CHECK: case op_CHECKb:
8846 				gtk_signal_connect(*slot, "toggled",
8847 					GTK_SIGNAL_FUNC(get_evt_1), r);
8848 				break;
8849 			case op_COLOR: case op_TCOLOR:
8850 				cpick_set_evt(*slot, r);
8851 				break;
8852 			case op_CSCROLL:
8853 			{
8854 				GtkAdjustment *xa, *ya;
8855 				get_scroll_adjustments(*slot, &xa, &ya);
8856 				gtk_signal_connect(GTK_OBJECT(xa), "value_changed",
8857 					GTK_SIGNAL_FUNC(get_evt_1), r);
8858 				gtk_signal_connect(GTK_OBJECT(ya), "value_changed",
8859 					GTK_SIGNAL_FUNC(get_evt_1), r);
8860 				break;
8861 			}
8862 			case op_CANVAS:
8863 		/* !!! This is sent after realize, or resize while realized */
8864 				gtk_signal_connect(*slot, "configure_event",
8865 					GTK_SIGNAL_FUNC(get_evt_conf), r);
8866 				break;
8867 			case op_TEXT:
8868 #if GTK_MAJOR_VERSION >= 2 /* In GTK+1, same handler as for GtkEntry */
8869 				g_signal_connect(gtk_text_view_get_buffer(
8870 					GTK_TEXT_VIEW(*slot)), "changed",
8871 					GTK_SIGNAL_FUNC(get_evt_1), r);
8872 				break;
8873 #endif
8874 			case op_ENTRY: case op_MLENTRY: case op_PENTRY:
8875 			case op_PATH: case op_PATHs:
8876 				gtk_signal_connect(*slot, "changed",
8877 					GTK_SIGNAL_FUNC(get_evt_1), r);
8878 				break;
8879 			case op_LISTCX:
8880 			{
8881 				listc_data *ld = slot[2];
8882 				ld->change = r;
8883 				break;
8884 			}
8885 			}
8886 		} // fallthrough
8887 		/* Remember that event needs triggering here */
8888 		/* Or remember a cleanup location */
8889 		case op_EVT_SCRIPT: case op_EVT_MULTI:
8890 		case op_TRIGGER: case op_CLEANUP:
8891 			widget = NULL;
8892 			break;
8893 		/* Allocate/copy memory */
8894 		case op_TALLOC: case op_TCOPY:
8895 		{
8896 			int l = *(int *)((char *)ddata + (int)pp[2]);
8897 			if (!l) continue;
8898 			dtail -= VVS(l);
8899 			if (op == op_TCOPY) memcpy(dtail, *(void **)v, l);
8900 			*(void **)v = dtail;
8901 			continue;
8902 		}
8903 		default:
8904 			/* Set nondefault border size */
8905 			if ((op >= op_BOR_0) && (op < op_BOR_LAST))
8906 				borders[op - op_BOR_0] = lp ? (int)v - DEF_BORDER : 0;
8907 			continue;
8908 		}
8909 		if (ref)
8910 		{
8911 			/* Finish pseudo widget */
8912 			if (pk == pk_UNREALV)
8913 			{
8914 				((swdata *)dtail)->value = tpad;
8915 				((swdata *)dtail)->id = wid;
8916 				wid = NULL;
8917 				pk = pk_UNREAL;
8918 			}
8919 			if (pk == pk_UNREAL)
8920 			{
8921 				widget = (void *)dtail;
8922 				((swdata *)dtail)->op = op;
8923 				pk = 0;
8924 			}
8925 			/* Remember this */
8926 			FIX_SLOT(r, widget ? (void *)widget : res);
8927 			/* Remember events */
8928 			if (ref > 2) ADD_SLOT(r, res, pp + lp - 3, NULL);
8929 			if (ref > 1) ADD_SLOT(r, res, pp + lp - 1, NULL);
8930 			/* Remember name */
8931 			if (scripted && cmds[op] && (cmds[op]->uop > 0) &&
8932 				(cmds[op]->uop != op_uLABEL))
8933 			{
8934 				static void *id_ALTNAME = WBrh(uALTNAME, 0);
8935 				dtail -= VVS(sizeof(swdata));
8936 				ADD_SLOT(r, dtail, &id_ALTNAME, dtail);
8937 				((swdata *)dtail)->op = op_uALTNAME;
8938 				((swdata *)dtail)->id = wid;
8939 				wid = NULL;
8940 			}
8941 		}
8942 		/* Pack this according to mode flags */
8943 		if (script) continue; // no packing in script mode
8944 		mods.cw = cw;
8945 		{
8946 			GtkWidget *w = do_prepare(widget, pk, &mods);
8947 			memset(&mods, 0, sizeof(mods));
8948 			if (((pk & pk_MASK) != pk_NONE) && do_pack(w, wp, pp, pk, tpad))
8949 				CT_POP(wp); // unstack
8950 		}
8951 		/* Stack this */
8952 		if (ct) CT_PUSH(wp, sw ? sw : widget, ct);
8953 		ct = 0;
8954 		sw = NULL;
8955 		if (gid && keygroup) // Nested group
8956 			keygroup = ini_setsection(&main_ini, keygroup, gid);
8957 	}
8958 }
8959 
do_destroy(void ** wdata)8960 static void do_destroy(void **wdata)
8961 {
8962 	void **pp, *v = NULL;
8963 	char *data = GET_DDATA(wdata);
8964 	v_dd *vdata = GET_VDATA(wdata);
8965 	int op;
8966 
8967 	if (vdata->done) return; // Paranoia
8968 	vdata->done = TRUE;
8969 
8970 	if (vdata->destroy)
8971 	{
8972 		void **base = vdata->destroy[0], **desc = vdata->destroy[1];
8973 		((evt_fn)desc[1])(GET_DDATA(base), base,
8974 			(int)desc[0] & WB_OPMASK, vdata->destroy);
8975 	}
8976 
8977 	for (wdata = GET_WINDOW(wdata); (pp = wdata[1]); wdata = NEXT_SLOT(wdata))
8978 	{
8979 		op = (int)*pp++;
8980 		v = pp[0];
8981 		if (op & WB_FFLAG) v = data + (int)v;
8982 		if (IS_UNREAL(wdata)) op = GET_UOP(wdata);
8983 		op &= WB_OPMASK;
8984 		switch (op)
8985 		{
8986 		case op_uFPICK: v = &((swdata *)*wdata)->strs; // Fallthrough
8987 		case op_CLEANUP: free(*(void **)v); break;
8988 		case op_CLIPFORM:
8989 		{
8990 			clipform_data *cd = *wdata;
8991 			gtk_target_list_unref(cd->targets);
8992 			free(cd);
8993 			break;
8994 		}
8995 		case op_uENTRY: case op_uPATHSTR:
8996 			v = &((swdata *)*wdata)->strs;
8997 			// Fallthrough
8998 		case op_TEXT: case op_FONTSEL: g_free(*(char **)v); break;
8999 		case op_TABLETBTN: conf_done(NULL); break;
9000 		case op_uMOUNT:
9001 			// !!! REMOUNT not expected in unreal state
9002 			run_destroy(((swdata *)*wdata)->strs);
9003 			break;
9004 		case op_REMOUNT:
9005 		{
9006 			void **where = *(void ***)v;
9007 			GtkWidget *what = gtk_bin_get_child(GTK_BIN(*wdata));
9008 
9009 			gtk_widget_reparent(what, where[0]);
9010 			gtk_widget_show(where[0]);
9011 			get_evt_1(where[0], NEXT_SLOT(where));
9012 			break;
9013 		}
9014 		case op_LISTCX: listcx_done(*wdata, wdata[2]); break;
9015 		case op_MAINWINDOW: gtk_main_quit(); break;
9016 		}
9017 	}
9018 }
9019 
do_query(char * data,void ** wdata,int mode)9020 static void *do_query(char *data, void **wdata, int mode)
9021 {
9022 	void **pp, *v = NULL;
9023 	int op;
9024 
9025 	for (; (pp = wdata[1]); wdata = NEXT_SLOT(wdata))
9026 	{
9027 		op = (int)*pp++;
9028 		v = op & (~0U << WB_LSHIFT) ? pp[0] : NULL;
9029 		if (op & WB_FFLAG) v = data + (int)v;
9030 		if (op & WB_NFLAG) v = *(void **)v; // dereference
9031 		if (IS_UNREAL(wdata)) op = GET_UOP(wdata);
9032 		op &= WB_OPMASK;
9033 		switch (op)
9034 		{
9035 		case op_FPICKpm:
9036 			fpick_get_filename(*wdata, v, PATHBUF, FALSE);
9037 			break;
9038 		case op_uFPICK:
9039 			strncpy0(v, ((swdata *)*wdata)->strs, PATHBUF);
9040 			break;
9041 		case op_SPINSLIDE: case op_SPINSLIDEa:
9042 		case op_SPIN: case op_SPINc: case op_SPINa:
9043 			*(int *)v = mode & 1 ? gtk_spin_button_get_value_as_int(
9044 				GTK_SPIN_BUTTON(*wdata)) : read_spin(*wdata);
9045 			break;
9046 		case op_uSPIN: case op_uFSPIN: case op_uSPINa: case op_uSCALE:
9047 		case op_uCHECK: case op_uCHECKb:
9048 		case op_uOPT: case op_uOPTD: case op_uRPACK: case op_uRPACKD:
9049 		case op_uCOLOR: case op_uMENUCHECK:
9050 			*(int *)v = ((swdata *)*wdata)->value;
9051 			if (op == op_uCHECKb) inifile_set_gboolean(pp[2], *(int *)v);
9052 			break;
9053 		case op_FSPIN:
9054 			*(int *)v = rint((mode & 1 ?
9055 #if GTK_MAJOR_VERSION == 3
9056 				gtk_spin_button_get_value(GTK_SPIN_BUTTON(*wdata)) :
9057 #else
9058 				GTK_SPIN_BUTTON(*wdata)->adjustment->value :
9059 #endif
9060 				read_float_spin(*wdata)) * 100);
9061 			break;
9062 		case op_TBTOGGLE:
9063 #if GTK_MAJOR_VERSION == 3 /* In GTK+1&2, same handler as for GtkToggleButton */
9064 			*(int *)v = gtk_toggle_tool_button_get_active(*wdata);
9065 			break;
9066 #endif
9067 		case op_CHECK: case op_CHECKb: case op_TOGGLE: case op_TBBOXTOG:
9068 			*(int *)v = gtk_toggle_button_get_active(*wdata);
9069 			if (op == op_CHECKb) inifile_set_gboolean(pp[2], *(int *)v);
9070 			break;
9071 		case op_TBRBUTTON:
9072 		{
9073 			GSList *group;
9074 			void **slot = wdata;
9075 
9076 			/* If reading radio group through an inactive slot */
9077 #if GTK_MAJOR_VERSION == 3
9078 			if (!gtk_toggle_tool_button_get_active(*wdata))
9079 			{
9080 				/* Let outer loop find active item */
9081 				if (mode <= 1) break;
9082 				/* Otherwise, find active item here */
9083 				group = gtk_radio_tool_button_get_group(*wdata);
9084 				/* !!! The ugly thing returns a group of _regular_
9085 				 * radiobuttons which sit inside toolbuttons */
9086 				while (group && !gtk_toggle_button_get_active(
9087 					GTK_TOGGLE_BUTTON(group->data)))
9088 					group = group->next;
9089 				if (!group) break; // impossible happened
9090 				slot = g_object_get_qdata(G_OBJECT(
9091 					gtk_widget_get_parent(group->data)), tool_key);
9092 			}
9093 #else /* #if GTK_MAJOR_VERSION <= 2 */
9094 			if (!gtk_toggle_button_get_active(*wdata))
9095 			{
9096 				/* Let outer loop find active item */
9097 				if (mode <= 1) break;
9098 				/* Otherwise, find active item here */
9099 				group = gtk_radio_button_group(*wdata);
9100 				while (group && !GTK_TOGGLE_BUTTON(group->data)->active)
9101 					group = group->next;
9102 				if (!group) break; // impossible happened
9103 				slot = gtk_object_get_user_data(
9104 					GTK_OBJECT(group->data));
9105 			}
9106 #endif
9107 			*(int *)v = TOOL_ID(slot);
9108 			break;
9109 		}
9110 		case op_MENUCHECK:
9111 			*(int *)v = gtk_check_menu_item_get_active(
9112 				GTK_CHECK_MENU_ITEM(*wdata));
9113 			break;
9114 		case op_MENURITEM:
9115 		{
9116 			GSList *group;
9117 			void **slot = wdata;
9118 
9119 			/* If reading radio group through an inactive slot */
9120 			if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(*wdata)))
9121 			{
9122 				/* Let outer loop find active item */
9123 				if (mode <= 1) break;
9124 				/* Otherwise, find active item here */
9125 				group = gtk_radio_menu_item_group(*wdata);
9126 				while (group && !gtk_check_menu_item_get_active(
9127 					GTK_CHECK_MENU_ITEM(group->data)))
9128 					group = group->next;
9129 				if (!group) break; // impossible happened
9130 #if GTK_MAJOR_VERSION == 3
9131 				slot = g_object_get_qdata(G_OBJECT(group->data),
9132 					tool_key);
9133 #else
9134 				slot = gtk_object_get_user_data(
9135 					GTK_OBJECT(group->data));
9136 #endif
9137 			}
9138 			*(int *)v = TOOL_ID(slot);
9139 			break;
9140 		}
9141 		case op_uMENURITEM:
9142 		{
9143 			void **slot = wdata;
9144 			int n;
9145 
9146 			/* If reading radio group through an inactive slot */
9147 			if (!((swdata *)*wdata)->value)
9148 			{
9149 				/* Let outer loop find active item */
9150 				if (mode <= 1) break;
9151 				/* Otherwise, find active item here */
9152 				while ((n = ((swdata *)slot[0])->range[0]))
9153 					slot -= n;
9154 				while (TRUE)
9155 				{
9156 					if (((swdata *)slot[0])->value) break;
9157 					if (!(n = ((swdata *)slot[0])->range[1]))
9158 						break;
9159 					slot += n;
9160 				}
9161 				if (!n) break; // impossible happened
9162 			}
9163 			*(int *)v = TOOL_ID(slot);
9164 			break;
9165 		}
9166 		case op_TLSPINPACK: case op_HEXENTRY: case op_EYEDROPPER:
9167 		case op_COLORLIST: case op_COLORLISTN: case op_GRADBAR:
9168 		case op_LISTCCr: case op_uLISTCC:
9169 		case op_LISTC: case op_LISTCd: case op_LISTCu:
9170 		case op_LISTCS: case op_LISTCX: case op_uLISTC:
9171 			break; // self-reading
9172 		case op_KEYBUTTON:
9173 		{
9174 			keybutton_data *dt = wdata[2];
9175 			*(char **)v = !dt->key ? NULL :
9176 				key_name(dt->key, dt->mod, dt->section);
9177 			break;
9178 		}
9179 		case op_COLOR:
9180 			*(int *)v = cpick_get_colour(*wdata, NULL);
9181 			break;
9182 		case op_TCOLOR:
9183 			*(int *)v = cpick_get_colour(*wdata, (int *)v + 1);
9184 			break;
9185 		case op_CSCROLL:
9186 		{
9187 			GtkAdjustment *xa, *ya;
9188 			int *xp = v;
9189 			get_scroll_adjustments(*wdata, &xa, &ya);
9190 			xp[0] = gtk_adjustment_get_value(xa);
9191 			xp[1] = gtk_adjustment_get_value(ya);
9192 			break;
9193 		}
9194 		case op_RPACK: case op_RPACKD:
9195 			*(int *)v = wj_radio_pack_get_active(*wdata);
9196 			break;
9197 		case op_OPT: case op_OPTD:
9198 			*(int *)v = wj_option_menu_get_history(*wdata);
9199 			break;
9200 		case op_COMBO:
9201 			*(int *)v = wj_combo_box_get_history(*wdata);
9202 			break;
9203 		case op_PCTCOMBO:
9204 			*(int *)v = 0; // default for error
9205 			sscanf(gtk_entry_get_text(
9206 				GTK_ENTRY(pctcombo_entry(*wdata))), "%d%%",
9207 				(int *)v);
9208 			break;
9209 		case op_COMBOENTRY:
9210 			*(const char **)v = comboentry_get_text(*wdata);
9211 			break;
9212 		case op_ENTRY: case op_MLENTRY:
9213 			*(const char **)v = gtk_entry_get_text(GTK_ENTRY(*wdata));
9214 			break;
9215 		case op_uENTRY: case op_uPATHSTR:
9216 			*(char **)v = ((swdata *)*wdata)->strs;
9217 			break;
9218 		case op_PENTRY: case op_PATH:
9219 			gtkncpy(v, gtk_entry_get_text(GTK_ENTRY(*wdata)),
9220 				op != op_PATH ? (int)pp[1] : PATHBUF);
9221 			break;
9222 		case op_PATHs:
9223 		{
9224 			char path[PATHBUF];
9225 			gtkncpy(path, gtk_entry_get_text(GTK_ENTRY(*wdata)), PATHBUF);
9226 			inifile_set(v, path);
9227 			break;
9228 		}
9229 		case op_TEXT:
9230 			g_free(*(char **)v);
9231 			*(char **)v = read_textarea(*wdata);
9232 			break;
9233 		case op_FONTSEL:
9234 		{
9235 			char **ss = v;
9236 			g_free(ss[0]);
9237 #if GTK_MAJOR_VERSION == 1
9238 			ss[0] = NULL;
9239 			if (gtk_font_selection_get_font(*wdata))
9240 #endif
9241 			ss[0] = gtk_font_selection_get_font_name(*wdata);
9242 			*(const char **)(ss + 1) =
9243 				gtk_font_selection_get_preview_text(*wdata);
9244 			break;
9245 		}
9246 		case op_MOUNT:
9247 			*(int *)v = !!gtk_bin_get_child(GTK_BIN(*wdata));
9248 			break;
9249 		case op_uMOUNT:
9250 			*(int *)v = !!((swdata *)*wdata)->strs;
9251 			break;
9252 		default: v = NULL; break;
9253 		}
9254 		if (mode > 1) return (v);
9255 	}
9256 	return (NULL);
9257 }
9258 
run_query(void ** wdata)9259 void run_query(void **wdata)
9260 {
9261 	v_dd *vdata = GET_VDATA(wdata);
9262 
9263 	/* Prod a focused widget which needs defocusing to update */
9264 	if (!vdata->script)
9265 	{
9266 		GtkWidget *w = GET_REAL_WINDOW(wdata);
9267 		GtkWidget *f = gtk_window_get_focus(GTK_WINDOW(w));
9268 		/* !!! No use to check if "fupslot" has focus - all dialogs with
9269 		 * such widget get destroyed after query anyway */
9270 		if (f && (GTK_IS_SPIN_BUTTON(f) || vdata->fupslot))
9271 			gtk_window_set_focus(GTK_WINDOW(w), NULL);
9272 	}
9273 
9274 	do_query(GET_DDATA(wdata), GET_WINDOW(wdata), 0);
9275 }
9276 
run_destroy(void ** wdata)9277 void run_destroy(void **wdata)
9278 {
9279 	v_dd *vdata = GET_VDATA(wdata);
9280 	if (vdata->done) return; // already destroyed
9281 	if (vdata->script) // simulated
9282 	{
9283 		do_destroy(wdata);
9284 		free(GET_DDATA(wdata));
9285 		return;
9286 	}
9287 	/* Work around WM misbehaviour, and save position & size if needed */
9288 	cmd_showhide(GET_WINDOW(wdata), FALSE);
9289 	gtk_widget_destroy(GET_REAL_WINDOW(wdata));
9290 }
9291 
cmd_reset(void ** slot,void * ddata)9292 void cmd_reset(void **slot, void *ddata)
9293 {
9294 // !!! Support only what actually used on, and their brethren
9295 	void *v, **pp, **wdata = slot;
9296 	int op, opf, group = FALSE;
9297 
9298 	for (; (pp = wdata[1]); wdata = NEXT_SLOT(wdata))
9299 	{
9300 		opf = op = (int)*pp++;
9301 		v = WB_GETLEN(op) ? pp[0] : NULL;
9302 		if (op & WB_FFLAG) v = (char *)ddata + (int)v;
9303 		if (op & WB_NFLAG) v = *(void **)v; // dereference
9304 		if (IS_UNREAL(wdata)) op = GET_UOP(wdata);
9305 		op &= WB_OPMASK;
9306 		switch (op)
9307 		{
9308 		case op_uOP:
9309 			/* If GROUP, reset up to one w/o script flag */
9310 			if ((opf & WB_OPMASK) == op_uOP)
9311 				group = !group || (opf & WB_SFLAG);
9312 			break;
9313 		case op_SPINSLIDE: case op_SPINSLIDEa:
9314 		case op_SPIN: case op_SPINc: case op_SPINa:
9315 			gtk_spin_button_set_value(*wdata, *(int *)v);
9316 			break;
9317 		case op_TLSPINPACK:
9318 		{
9319 			void **vp = wdata[2];
9320 			int i, n = (int)pp[1];
9321 
9322 			*vp = NULL; // Lock
9323 			for (i = 0; i < n; i++)
9324 			{
9325 				GtkSpinButton *spin = vp[i * 2 + 1];
9326 				int *p = (int *)v + i * 3;
9327 
9328 				gtk_spin_button_set_value(spin, *p);
9329 				/* Value might get clamped, and slot is
9330 				 * self-reading so should reflect that */
9331 				*p = gtk_spin_button_get_value_as_int(spin);
9332 			}
9333 			*vp = wdata; // Unlock
9334 			break;
9335 		}
9336 		case op_uSPIN: case op_uSPINa:
9337 		{
9338 			swdata *sd = *wdata;
9339 			int n = *(int *)v;
9340 			sd->value = n > sd->range[1] ? sd->range[1] :
9341 				n < sd->range[0] ? sd->range[0] : n;
9342 			break;
9343 		}
9344 		case op_TBTOGGLE:
9345 #if GTK_MAJOR_VERSION == 3 /* In GTK+1&2, same handler as for GtkToggleButton */
9346 			gtk_toggle_tool_button_set_active(*wdata, *(int *)v);
9347 			break;
9348 #endif
9349 		case op_CHECK: case op_CHECKb: case op_TOGGLE:
9350 		case op_TBBOXTOG:
9351 			gtk_toggle_button_set_active(*wdata, *(int *)v);
9352 			break;
9353 		case op_uCHECK: case op_uCHECKb:
9354 			((swdata *)*wdata)->value = !!*(int *)v;
9355 			break;
9356 		case op_OPT:
9357 			/* !!! No support for discontinuous lists, for now */
9358 			wj_option_menu_set_history(*wdata, *(int *)v);
9359 			break;
9360 		case op_OPTD:
9361 			opt_reset(wdata, ddata, *(int *)v);
9362 			break;
9363 		case op_uOPT: case op_uOPTD:
9364 		{
9365 			swdata *sd = *wdata;
9366 			int n;
9367 
9368 			if (op == op_uOPTD)
9369 			{
9370 				char **strs = sd->strs =
9371 					*(char ***)((char *)ddata + (int)pp[1]);
9372 				for (n = 0; strs[n]; n++); // Count strings
9373 				sd->cnt = n;
9374 			}
9375 
9376 			n = *(int *)v;
9377 			sd->value = (n < 0) || (n >= sd->cnt) ||
9378 				!((char **)sd->strs)[n][0] ? 0 : n;
9379 			break;
9380 		}
9381 		case op_LISTCCr:
9382 			listcc_reset(wdata, -1);
9383 #ifdef U_LISTS_GTK1
9384 			/* !!! Or the changes will be ignored if the list wasn't
9385 			 * yet displayed (as in inactive dock tab) - WJ */
9386 			gtk_widget_queue_resize(*wdata);
9387 #endif
9388 			break;
9389 		/* op_uLISTCC needs no resetting */
9390 		case op_LISTC: case op_LISTCd: case op_LISTCu:
9391 		case op_LISTCS: case op_LISTCX:
9392 			listc_reset(*wdata, wdata[2]);
9393 			break;
9394 		case op_uLISTC:
9395 			ulistc_reset(wdata);
9396 			break;
9397 		case op_CSCROLL:
9398 		{
9399 			GtkAdjustment *xa, *ya;
9400 			int *xp = v;
9401 			get_scroll_adjustments(*wdata, &xa, &ya);
9402 #if GTK_MAJOR_VERSION == 3
9403 			{
9404 				/* This is to change both before triggering any */
9405 				guint id = g_signal_lookup("value_changed",
9406 					G_TYPE_FROM_INSTANCE(xa));
9407 				g_signal_handlers_block_matched(G_OBJECT(xa),
9408 					G_SIGNAL_MATCH_ID, id, 0, NULL, NULL, NULL);
9409 				g_signal_handlers_block_matched(G_OBJECT(ya),
9410 					G_SIGNAL_MATCH_ID, id, 0, NULL, NULL, NULL);
9411 				gtk_adjustment_set_value(xa, xp[0]);
9412 				gtk_adjustment_set_value(ya, xp[1]);
9413 				g_signal_handlers_unblock_matched(G_OBJECT(xa),
9414 					G_SIGNAL_MATCH_ID, id, 0, NULL, NULL, NULL);
9415 				g_signal_handlers_unblock_matched(G_OBJECT(ya),
9416 					G_SIGNAL_MATCH_ID, id, 0, NULL, NULL, NULL);
9417 			}
9418 #else /* #if GTK_MAJOR_VERSION <= 2 */
9419 			xa->value = xp[0];
9420 			ya->value = xp[1];
9421 #endif
9422 			gtk_adjustment_value_changed(xa);
9423 			gtk_adjustment_value_changed(ya);
9424 			break;
9425 		}
9426 		case op_COMBOENTRY:
9427 			comboentry_reset(*wdata, v, *(char ***)(ddata + (int)pp[1]));
9428 			break;
9429 		case op_ENTRY: case op_MLENTRY:
9430 			gtk_entry_set_text(*wdata, *(char **)v);
9431 			// Replace transient buffer - it may get freed on return
9432 			*(const char **)v = gtk_entry_get_text(*wdata);
9433 			break;
9434 		case op_PATHs:
9435 			v = inifile_get(v, ""); // read and fallthrough
9436 		case op_PENTRY: case op_PATH:
9437 			set_path(*wdata, v, PATH_VALUE);
9438 			break;
9439 		case op_RGBIMAGEP:
9440 		{
9441 			rgbimage_data *rd = wdata[2];
9442 			rd->rgb = v; // Size is fixed, but update source
9443 			if (GTK_WIDGET_REALIZED(*wdata)) reset_rgbp(*wdata, rd);
9444 			break;
9445 		}
9446 		case op_CANVASIMGB:
9447 		{
9448 			rgbimage_data *rd = wdata[2];
9449 			int nw, *xp = (int *)(ddata + (int)pp[1]);
9450 
9451 			nw = (rd->w ^ xp[0]) | (rd->h ^ xp[1]);
9452 			rd->rgb = v;
9453 			rd->w = xp[0];
9454 			rd->h = xp[1];
9455 			rd->bkg = xp[2];
9456 			if (nw) wjcanvas_size(*wdata, xp[0], xp[1]);
9457 			wjcanvas_uncache(*wdata, NULL);
9458 			gtk_widget_queue_draw(*wdata);
9459 			break;
9460 		}
9461 		case op_FCIMAGEP:
9462 		{
9463 			fcimage_data *fd = wdata[2];
9464 			fd->rgb = v; // Update source, leave other parts be
9465 			if (GTK_WIDGET_REALIZED(*wdata)) reset_fcimage(*wdata, fd);
9466 			break;
9467 		}
9468 		case op_KEYMAP:
9469 			keymap_reset(wdata[2]);
9470 #if GTK_MAJOR_VERSION >= 2
9471 			gtk_signal_emit_by_name(GTK_OBJECT(gtk_widget_get_toplevel(
9472 				GET_REAL_WINDOW(wdata_slot(wdata)))),
9473 				"keys_changed", NULL);
9474 #endif
9475 			break;
9476 #if 0 /* Not needed for now */
9477 		case op_FPICKpm:
9478 			fpick_set_filename(*wdata, v, FALSE);
9479 			break;
9480 		case op_FSPIN:
9481 			gtk_spin_button_set_value(*wdata, *(int *)v * 0.01);
9482 			break;
9483 		case op_uENTRY: case op_uPATHSTR:
9484 			// Replace transient buffer - it may get freed on return
9485 			*(char **)v = set_uentry(*wdata, *(char **)v);
9486 			cmd_event(wdata, op_EVT_CHANGE);
9487 			break;
9488 		case op_TBRBUTTON:
9489 			if (*(int *)v == TOOL_ID(wdata))
9490 #if GTK_MAJOR_VERSION == 3
9491 				gtk_toggle_tool_button_set_active(*wdata, TRUE);
9492 #else
9493 				gtk_toggle_button_set_active(*wdata, TRUE);
9494 #endif
9495 			break;
9496 		case op_MENURITEM:
9497 			if (*(int *)v != TOOL_ID(wdata)) break;
9498 			// Fallthrough
9499 		case op_MENUCHECK:
9500 			gtk_check_menu_item_set_active(*wdata,
9501 				op == op_MENURITEM ? TRUE : *(int *)v);
9502 			break;
9503 		case op_PCTCOMBO:
9504 			/* Same as in cmd_set() */
9505 			break;
9506 		case op_RPACK: case op_RPACKD:
9507 		case op_COLORLIST: case op_COLORLISTN:
9508 // !!! No ready setter functions for these (and no need of them yet)
9509 			break;
9510 		case op_COLOR:
9511 			cpick_set_colour(*wdata, *(int *)v, 255);
9512 			break;
9513 		case op_TCOLOR:
9514 			cpick_set_colour(*wdata, ((int *)v)[0], ((int *)v)[1]);
9515 			break;
9516 		case op_RGBIMAGE:
9517 		{
9518 			rgbimage_data *rd = wdata[2];
9519 			int *wh = (int *)(ddata + (int)pp[1]);
9520 			rd->rgb = v;
9521 			rd->w = wh[0];
9522 			rd->h = wh[1];
9523 			cmd_repaint(wdata);
9524 			break;
9525 		}
9526 #endif
9527 		}
9528 		if (!group) return;
9529 	}
9530 }
9531 
cmd_sensitive(void ** slot,int state)9532 void cmd_sensitive(void **slot, int state)
9533 {
9534 	int op;
9535 
9536 	if (IS_UNREAL(slot))
9537 	{
9538 // !!! COLUMNs should redirect to their list slot instead
9539 		((swdata *)slot[0])->insens = !state;
9540 		return;
9541 	}
9542 	op = GET_OP(slot);
9543 	if (op >= op_EVT_0) return; // only widgets
9544 	gtk_widget_set_sensitive(get_wrap(slot), state);
9545 }
9546 
midmatch(const char * s,const char * v,int l)9547 static int midmatch(const char *s, const char *v, int l)
9548 {
9549 	while ((s = strchr(s, ' ')))
9550 	{
9551 		s += strspn(s, " (");
9552 		if (!strncasecmp(s, v, l)) break;
9553 	}
9554 	return (!!s);
9555 }
9556 
9557 static int find_string(swdata *sd, char *s, int l, int column);
9558 
find_slot(void ** slot,char * id,int l,int mlevel)9559 void **find_slot(void **slot, char *id, int l, int mlevel)
9560 {
9561 	void **start = slot, **where = NULL;
9562 	char buf[64], *nm, *ts;
9563 	int op, n, p = INT_MAX;
9564 
9565 	for (; slot[1]; slot = NEXT_SLOT(slot))
9566 	{
9567 		op = GET_OP(slot);
9568 		if (op == op_uOPNAME) break; // ENDSCRIPT marker
9569 		if (mlevel >= 0) // Searching menu items
9570 		{
9571 			/* Reading the descriptors, so ignore unreal state */
9572 // !!! Or maybe switch/case?
9573 			if ((op == op_uALTNAME) || (op == op_uMENUITEM))
9574 				nm = ((swdata *)slot[0])->id;
9575 			else if ((op == op_SUBMENU) || (op == op_ESUBMENU) ||
9576 				(op == op_SSUBMENU))
9577 			{
9578 				nm = GET_DESCV(slot, 1);
9579 				// Remove shortcut marker from toplevel items
9580 				if ((ts = strchr(nm, '_'))) nm = wjstrcat(buf,
9581 					sizeof(buf), nm, ts - nm, ts + 1, NULL);
9582 			}
9583 			else if ((op == op_MENUITEM) || (op == op_MENUCHECK) ||
9584 				(op == op_MENURITEM)) nm = GET_DESCV(slot, 3);
9585 			else continue;
9586 			if (mlevel) // Searching a sublevel
9587 			{
9588 				n = strspn(nm, "/");
9589 				if ((n < mlevel) && (op != op_uALTNAME))
9590 					break; // level end
9591 				else if (n != mlevel)
9592 					continue; // submenu or direct altname
9593 				nm += n;
9594 			}
9595 		}
9596 		else // Searching pseudo widgets
9597 		{
9598 			if (!IS_UNREAL(slot)) continue;
9599 			if (GET_UOP(slot) == op_uOP)
9600 			{
9601 				// Ignore dummy slots
9602 				if (op != op_uOP) continue;
9603 				// Skip groups in flat search
9604 				if (mlevel == MLEVEL_FLAT) continue;
9605 				// Stop on new group in block search
9606 				if (mlevel == MLEVEL_BLOCK) break;
9607 			}
9608 			// Ignore anything but groups in group search
9609 			else if (mlevel == MLEVEL_GROUP) continue;
9610 			nm = ((swdata *)slot[0])->id;
9611 			if (!nm) continue; // No name
9612 		}
9613 		/* Match empty option name to an empty ID (default) */
9614 		if (!l)
9615 		{
9616 			if (nm[0]) continue;
9617 			where = slot;
9618 			break;
9619 		}
9620 		/* In a flattened widget, match contents */
9621 		if ((nm[0] == ':') && !nm[1] && (op == op_uALTNAME))
9622 		{
9623 			void **w = origin_slot(slot);
9624 			if (!IS_UNREAL(w)) continue;
9625 			n = GET_UOP(w);
9626 			// Allow only static lists for now
9627 			if ((n != op_uOPT) && (n != op_uRPACK)) continue;
9628 			n = find_string(w[0], id, l, FALSE);
9629 			if (n < 0) continue;
9630 			// Use matched string as widget name
9631 			nm = ((char **)((swdata *)w[0])->strs)[n];
9632 		}
9633 		/* Match at beginning, preferring shortest word */
9634 		if (!strncasecmp(nm, id, l))
9635 		{
9636 			int d = l + strcspn(nm + l, " .,()");
9637 			// Prefer if no other words
9638 			d = d + d + !!nm[d + strcspn(nm + d, " .,()")];
9639 			if (d >= p) continue;
9640 			p = d;
9641 			where = slot;
9642 		}
9643 		else if (where);
9644 		/* Match at word beginning */
9645 		else if (mlevel && midmatch(nm, id, l)) where = slot;
9646 	}
9647 	/* Resolve alternative name */
9648 	if (where && (GET_OP(where) == op_uALTNAME))
9649 	{
9650 		nm = ((swdata *)where[0])->id;
9651 		if ((nm[0] != ':') || nm[1]) // Leave flattening markers be
9652 			where = origin_slot(where);
9653 	}
9654 	/* Try in-group searching */
9655 	if (!where && (mlevel == MLEVEL_FLAT) && (ts = memchr(id, '/', l)) &&
9656 		(ts != id) && (id + l - ts > 1))
9657 	{
9658 		where = find_slot(start, id, ts - id, MLEVEL_GROUP);
9659 		if (where) where = find_slot(NEXT_SLOT(where), ts + 1,
9660 			id + l - ts - 1, MLEVEL_BLOCK);
9661 	}
9662 	return (where);
9663 }
9664 
9665 /* Match string to list column of strings, or to widget's string list */
find_string(swdata * sd,char * s,int l,int column)9666 static int find_string(swdata *sd, char *s, int l, int column)
9667 {
9668 	char *tmp;
9669 	col_data *c = NULL;
9670 	int i, ll, p = INT_MAX, n = sd->cnt;
9671 
9672 	if (!s || !l) return (-1); // Error
9673 	if (column)
9674 	{
9675 		c = sd->strs;
9676 		if (!c) return (-1); // Not linked
9677 		column = n;
9678 		/* List length */
9679 		n = *(int *)(c->ddata + (int)GET_DESCV(c->r, 2));
9680 	}
9681 
9682 	for (ll = -1 , i = 0; i < n; i++)
9683 	{
9684 		tmp = c ? get_cell(c, i, column) : ((char **)sd->strs)[i];
9685 		if (!tmp[0]) continue;
9686 		/* Match at beginning */
9687 		if (!strncasecmp(tmp, s, l))
9688 		{
9689 			int k = strlen(tmp);
9690 			if (k >= p) continue;
9691 			p = k;
9692 		}
9693 		else if (ll >= 0) continue;
9694 		/* Match at word beginning */
9695 		else if (!midmatch(tmp, s, l)) continue;
9696 		ll = i;
9697 	}
9698 	return (ll);
9699 }
9700 
9701 /* Resolve parameter chaining */
unchain_p(char ** strs)9702 static char **unchain_p(char **strs)
9703 {
9704 	while (*strs == (void *)strs) strs = (void *)strs[1];
9705 	return (strs);
9706 }
9707 
9708 /* Parse a parenthesized list of tuples into an array of ints
9709  * List may continue in next script positions */
multi_parse(char * s0)9710 static multi_ext *multi_parse(char *s0)
9711 {
9712 	multi_ext *mx;
9713 	char c, *ss, *tmp, **strs;
9714 	int w = 0, mw = INT_MAX, fp = -1;
9715 	int n, l, cnt, sc, err, *ix, **rows;
9716 
9717 	/* Crudely count the parts */
9718 	if (s0[0] != '(') return (NULL); // Wrong
9719 	ss = s0 + 1;
9720 	cnt = sc = 0;
9721 	strs = script_cmds;
9722 	while (TRUE)
9723 	{
9724 		c = *ss++;
9725 		if (c == ')') break;
9726 		if (c == ',') cnt++;
9727 		if (!c)
9728 		{
9729 			strs = unchain_p(strs);
9730 			ss = *strs++;
9731 			if (!ss) return (NULL); // Unterminated
9732 			sc++;
9733 		}
9734 	}
9735 
9736 	/* Allocate */
9737 	mx = calloc(1, sizeof(multi_ext) + sizeof(int *) * (sc + 2 - 1) +
9738 		sizeof(int) * (cnt + sc * 2 + 2 + 1));
9739 	if (!mx) return (NULL);
9740 	ix = (void *)(mx->rows + sc + 2);
9741 	// Extremely unlikely, but why not make sure
9742 	if (ALIGNOF(int) > ALIGNOF(int *)) ix = ALIGNED(ix, ALIGNOF(int));
9743 
9744 	/* Parse & fill */
9745 	sc = l = err = 0;
9746 	rows = mx->rows;
9747 	strs = script_cmds;
9748 	for (ss = s0 + 1; ss; strs = unchain_p(strs) , ss = *strs++)
9749 	{
9750 		if (!ss[0]) continue; // Empty
9751 		if ((ss[0] == ')') && !ss[1]) break; // Lone ')'
9752 		rows[l++] = ix++;
9753 		n = 0; err = 1;
9754 		while (TRUE)
9755 		{
9756 			ix[n] = strtol(ss, &tmp, 10);
9757 			if (tmp == ss) break; // Error
9758 			if (*tmp == '.') // Maybe floating - use fixedpoint
9759 			{
9760 				// !!! Only one fixedpoint column allowed for now
9761 				if ((fp >= 0) && (fp != n)) break;
9762 
9763 				ix[fp = n] = (int)(g_strtod(ss, &tmp) *
9764 					MAX_PRESSURE + 0.5);
9765 			}
9766 			n++;
9767 			ss = tmp + 1;
9768 			if (*tmp == ',') continue;
9769 			if (*tmp && ((*tmp != ')') || tmp[1])) break; // Error
9770 			err = 0; // Valid tuple
9771 			*(ix - 1) = n; // Store count
9772 			ix += n; // Skip over
9773 			if (w < n) w = n; // Update max
9774 			if (mw > n) mw = n; // Update min
9775 			break;
9776 		}
9777 		if (err || *tmp) break;
9778 	}
9779 	if (err || !ss || (l <= 0))
9780 	{
9781 		free(mx);
9782 		return (NULL);
9783 	}
9784 
9785 	/* Finalize */
9786 	script_cmds = strs;
9787 
9788 	mx->nrows = l;
9789 	mx->ncols = w;
9790 	mx->mincols = mw;
9791 	mx->fractcol = fp;
9792 	return (mx);
9793 }
9794 
cmd_setstr(void ** slot,char * s)9795 int cmd_setstr(void **slot, char *s)
9796 {
9797 	void *v;
9798 	char *tmp, *st = NULL;
9799 	int op, ll = 0, res = 1;
9800 
9801 	/* If see a list and prepared to handle it, do so */
9802 	if (s && (s[0] == '(') && (v = op_slot(slot, op_EVT_MULTI))) slot = v;
9803 
9804 	op = GET_OP(slot);
9805 	if (IS_UNREAL(slot)) op = GET_UOP(slot);
9806 	switch (op)
9807 	{
9808 	case op_uFPICK: case op_uPATHSTR:
9809 		if (!s) s = "";
9810 		// Commandline is already in system encoding
9811 		if (!cmd_mode) s = st = gtkncpy(NULL, s, PATHBUF);
9812 		cmd_setv(slot, s, op == op_uFPICK ? FPICK_VALUE : ENTRY_VALUE);
9813 		g_free(st);
9814 		goto done;
9815 	case op_ENTRY: case op_uENTRY:
9816 		if (!s) s = "";
9817 		// Commandline is in system encoding
9818 		if (cmd_mode) s = st = gtkuncpy(NULL, s, 0);
9819 		cmd_setv(slot, s, ENTRY_VALUE);
9820 		g_free(st);
9821 		goto done;
9822 	case op_CHECK: case op_CHECKb: case op_TOGGLE:
9823 	case op_TBTOGGLE: case op_TBBOXTOG: case op_TBRBUTTON:
9824 	case op_MENUCHECK: case op_MENURITEM:
9825 	case op_uCHECK: case op_uCHECKb:
9826 	case op_uMENUCHECK: case op_uMENURITEM:
9827 		ll = !s ? TRUE : !s[0] ? FALSE : str2bool(s);
9828 		if (ll < 0) return (-1); // Error
9829 		break;
9830 	case op_TXTCOLUMN: case op_XTXTCOLUMN:
9831 		ll = TRUE;
9832 		// Fallthrough
9833 	case op_uOPT: case op_uOPTD: case op_uRPACK: case op_uRPACKD:
9834 		if (!s) return (-1); // Error
9835 		ll = find_string(slot[0], s, strlen(s), ll);
9836 		if (ll < 0) return (-1); // Error
9837 		break;
9838 	case op_BUTTON: case op_TBBUTTON: case op_uBUTTON:
9839 	case op_MENUITEM: case op_uMENUITEM:
9840 		ll = res = 0; // No use for parameter
9841 		break;
9842 	case op_FSPIN: case op_uFSPIN:
9843 		if (s && s[0])
9844 		{
9845 			double a = g_strtod(s, &tmp);
9846 			if (*tmp) return (-1); // Error
9847 			ll = rint(a * 100);
9848 			break;
9849 		}
9850 		// Fallthrough
9851 	case op_SPIN: case op_SPINc: case op_SPINa:
9852 	case op_SPINSLIDE: case op_SPINSLIDEa:
9853 	case op_uSPIN: case op_uSPINa:
9854 	case op_LISTCCr: case op_uLISTCC: case op_uLISTC: case op_IDXCOLUMN:
9855 		if (!s) return (-1); // Error
9856 		ll = 0; // Default
9857 		if (s[0])
9858 		{
9859 			ll = strtol(s, &tmp, 10);
9860 			if (*tmp) return (-1); // Error
9861 		}
9862 		break;
9863 	case op_uCOLOR:
9864 		ll = parse_color(s);
9865 		if (ll < 0) return (-1); // Error
9866 		break;
9867 	case op_uSCALE:
9868 	{
9869 		swdata *sd = slot[0];
9870 
9871 		if (!s || !s[0]) return (-1); // Error
9872 		if (strchr(s, '%')) /* "w=125%" */
9873 		{
9874 			ll = strtol(s, &tmp, 10);
9875 			if (*tmp != '%') return (-1); // Error
9876 			ll = (ll * (int)sd->strs) / 100;
9877 		}
9878 		else if ((s[0] == 'x') || (s[0] == 'X')) /* "w=x1.25" */
9879 		{
9880 			double a = g_strtod(s + 1, &tmp);
9881 			if (*tmp) return (-1); // Error
9882 			ll = rint((int)sd->strs * a);
9883 		}
9884 		else /* "w=200" */
9885 		{
9886 			ll = strtol(s, &tmp, 10);
9887 			if (*tmp) return (-1); // Error
9888 		}
9889 		sd->cnt = 3; // Value is being set directly
9890 		break;
9891 	}
9892 	case op_EVT_MULTI:
9893 		if ((v = multi_parse(s)))
9894 		{
9895 			void **base = slot[0], **desc = slot[1];
9896 			res = ((evtxr_fn)desc[1])(GET_DDATA(base), base,
9897 				(int)desc[0] & WB_OPMASK, slot, v);
9898 			if (res >= 0) free(v); // Handler can ask to keep it
9899 			if (res) return (1);
9900 		}
9901 		// !!! Fallthrough to fail
9902 	default: return (-1); // Error: cannot handle
9903 	}
9904 	/* From column to list */
9905 	if ((op >= op_COLUMN_0) && (op < op_COLUMN_LAST))
9906 		slot = ((col_data *)((swdata *)slot[0])->strs)->r;
9907 	/* Set value to widget */
9908 	cmd_set(slot, ll);
9909 done:	cmd_event(slot, op_EVT_SCRIPT); // Notify
9910 	return (res);
9911 }
9912 
tbar_event(void ** slot,int what)9913 static int tbar_event(void **slot, int what)
9914 {
9915 	void **base, **desc, **tbar = NULL;
9916 	int op = GET_OP(slot);
9917 	if (IS_UNREAL(slot)) op = GET_UOP(slot);
9918 	switch (op)
9919 	{
9920 	case op_TBBUTTON: case op_TBTOGGLE: case op_TBRBUTTON: case op_TBBOXTOG:
9921 	case op_MENUITEM: case op_MENUCHECK: case op_MENURITEM:
9922 		tbar = slot[2];
9923 		break;
9924 	case op_uMENUITEM: case op_uMENUCHECK: case op_uMENURITEM:
9925 		tbar = ((swdata *)slot[0])->strs;
9926 		break;
9927 	}
9928 	if (tbar) tbar = op_slot(tbar, what);
9929 	if (!tbar || ((what == op_EVT_CLICK) && (WB_GETLEN(GET_OPF(slot)) < 5)))
9930 		return (-1); // Fail
9931 	/* Call event at saved slot, for this slot */
9932 	base = tbar[0]; desc = tbar[1];
9933 	((evt_fn)desc[1])(GET_DDATA(base), base, (int)desc[0] & WB_OPMASK, slot);
9934 	return (0);
9935 }
9936 
cmd_run_script(void ** slot,char ** strs)9937 int cmd_run_script(void **slot, char **strs)
9938 {
9939 	char *opt, *tmp, **err = NULL;
9940 	void **wdata;
9941 	int op, ll, maybe;
9942 
9943 	/* Resolve slot */
9944 	op = GET_OP(slot);
9945 	if (IS_UNREAL(slot)) op = GET_UOP(slot);
9946 	if (op == op_uMOUNT) slot = ((swdata *)*slot)->strs;
9947 	else if (op == op_MOUNT) slot = slot[2];
9948 	if (!slot) return (1); // Paranoia
9949 
9950 	/* Step through options */
9951 	while (strs = unchain_p(strs) , opt = *strs)
9952 	{
9953 		/* Stop on commands and empty strings */
9954 		if (!opt[0] || (opt[0] == '-')) break;
9955 		strs++;
9956 		/* Stop on dialog close */
9957 		if ((opt[0] == ':') && !opt[1]) break;
9958 		/* Have option: first, parse it */
9959 		opt += (maybe = opt[0] == '.'); // Optional if preceded by "."
9960 		// Expect "(list)", "name=value", or "name:"
9961 		ll = opt[0] == '(' ? 0 : strcspn(opt, "=:");
9962 		/* Now, find target for the option */
9963 		wdata = find_slot(slot, opt, ll, MLEVEL_FLAT);
9964 		/* Raise an error if no match */
9965 		if (!wdata)
9966 		{
9967 			if (maybe) continue; // Ignore optional options
9968 			err = strs;
9969 			break;
9970 		}
9971 		/* For flattened lists, uALTNAME gets returned */
9972 		op = GET_OP(wdata);
9973 		wdata = origin_slot(wdata);
9974 		/* Leave insensitive slots alone */
9975 		if (!cmd_checkv(wdata, SLOT_SENSITIVE)) continue;
9976 // !!! Or maybe raise an error, too?
9977 		script_cmds = strs; // For nested dialog
9978 		/* Set value to flattened list */
9979 		if (op == op_uALTNAME)
9980 		{
9981 			ll = find_string(wdata[0], opt, ll, FALSE); // Cannot fail
9982 			cmd_set(wdata, ll);
9983 			cmd_event(wdata, op_EVT_SCRIPT); // Notify
9984 		}
9985 		/* Activate right-click handler */
9986 		else if (opt[ll] == ':') ll = tbar_event(wdata, op_EVT_CLICK);
9987 		/* Set value to slot */
9988 		else ll = cmd_setstr(wdata, !opt[ll] ? NULL :
9989 			opt + ll + (opt[ll] == '='));
9990 		/* Raise an error if invalid value */
9991 		if (ll < 0)
9992 		{
9993 			err = strs;
9994 			break;
9995 		}
9996 		strs = script_cmds; // Nested dialogs can consume options
9997 		if (user_break) break;
9998 	}
9999 	script_cmds = strs; // Stopped at here
10000 
10001 	/* An error happened - report it */
10002 	if (err)
10003 	{
10004 		err--; // Previous option caused the error
10005 		/* Find name-bearing slot */
10006 		slot = wdata;
10007 		if (slot && !IS_UNREAL(slot)) slot = op_slot(wdata, op_uALTNAME);
10008 		tmp = g_strdup_printf(!wdata ? _("'%s' does not match any widget") :
10009 			_("'%s' value does not fit '%s' widget"), *err,
10010 			slot ? ((swdata *)slot[0])->id : NULL);
10011 		alert_box(_("Error"), tmp, NULL);
10012 		g_free(tmp);
10013 		return (-1);
10014 	}
10015 
10016 	/* Now activate the OK handler if any */
10017 	for (wdata = slot; wdata[1]; wdata = NEXT_SLOT(wdata))
10018 		if (IS_UNREAL(wdata) && ((GET_UOP(wdata) == op_uOKBTN) ||
10019 			(GET_UOP(wdata) == op_uFPICK))) break;
10020 	if (wdata[1])
10021 	{
10022 		cmd_event(wdata, op_EVT_OK);
10023 		/* !!! The memory block is likely to be freed at this point */
10024 		return (0);
10025 	}
10026 
10027 	return (1);
10028 }
10029 
cmd_showhide(void ** slot,int state)10030 void cmd_showhide(void **slot, int state)
10031 {
10032 	GtkWidget *wrap, *ws = NULL;
10033 	void **keymap = NULL;
10034 	int raise = FALSE, unfocus = FALSE, mx = FALSE;
10035 
10036 	if (GET_OP(slot) == op_WDONE) slot = NEXT_SLOT(slot); // skip head noop
10037 	if (IS_UNREAL(slot))
10038 	{
10039 		if ((GET_OP(PREV_SLOT(slot)) == op_WDONE) && state)
10040 		{
10041 			v_dd *vdata = GET_VDATA(PREV_SLOT(slot));
10042 			/* Script is run when the window is first displayed */
10043 			if (vdata->run) return; // Redisplay
10044 			vdata->run = TRUE;
10045 			if ((cmd_run_script(slot, vdata->script) < 0) &&
10046 				(GET_UOP(slot) == op_uWINDOW))
10047 				run_destroy(PREV_SLOT(slot)); // On error
10048 		}
10049 		return;
10050 	}
10051 	if (GET_OP(slot) >= op_EVT_0) return; // only widgets
10052 	wrap = get_wrap(slot); // For *some* pkf_PARENT widgets, hide/show their parent
10053 	if (!GTK_WIDGET_VISIBLE(wrap) ^ !!state) return; // no use
10054 	if (GET_OP(PREV_SLOT(slot)) == op_WDONE) // toplevels are special
10055 	{
10056 		v_dd *vdata = GET_VDATA(PREV_SLOT(slot));
10057 		GtkWidget *w = GTK_WIDGET(slot[0]);
10058 
10059 		if (state) // show - apply stored size, position, raise, unfocus
10060 		{
10061 			gtk_window_set_transient_for(GTK_WINDOW(w), vdata->tparent);
10062 #if GTK_MAJOR_VERSION == 3
10063 			if (vdata->ininame) gtk_window_move(GTK_WINDOW(w),
10064 #else
10065 			if (vdata->ininame) gtk_widget_set_uposition(w,
10066 #endif
10067 				vdata->xywh[0], vdata->xywh[1]);
10068 			else vdata->ininame = ""; // first time
10069 			gtk_window_set_default_size(GTK_WINDOW(w),
10070 				vdata->xywh[2] ? vdata->xywh[2] : -1,
10071 				vdata->xywh[3] ? vdata->xywh[3] : -1);
10072 			if (vdata->modal) gtk_window_set_modal(GTK_WINDOW(w), TRUE);
10073 			/* Prepare to do postponed actions */
10074 			mx = vdata->xywh[4];
10075 #if GTK_MAJOR_VERSION >= 2
10076 			/* !!! When doing maximize, we have to display window
10077 			 * contents after window itself, or some widgets may get
10078 			 * locked into wrong size by premature first-time size
10079 			 * request & allocation within unmaximized window.
10080 			 * On GTK+1 the resize mechanism is quite different and
10081 			 * such reordering only makes the result even worse; what
10082 			 * can cause proper resizing there, isn't found yet - WJ */
10083 			if (mx)
10084 			{
10085 				ws = gtk_bin_get_child(GTK_BIN(w));
10086 				if (ws && GTK_WIDGET_VISIBLE(ws))
10087 					widget_showhide(ws, FALSE);
10088 				else ws = NULL;
10089 			}
10090 #endif
10091 			keymap = vdata->keymap;
10092 			raise = vdata->raise;
10093 			unfocus = vdata->unfocus;
10094 			vdata->raise = vdata->unfocus = FALSE;
10095 		}
10096 		else // hide - remember size & position
10097 		{
10098 			/* !!! These reads also do gdk_flush() which, followed by
10099 			 * set_transient(NULL), KDE somehow needs for restoring
10100 			 * focus from fileselector back to pref window - WJ */
10101 			if (!(vdata->xywh[4] = is_maximized(w)))
10102 			{
10103 #if GTK_MAJOR_VERSION == 3
10104 				gtk_window_get_size(GTK_WINDOW(w),
10105 #else
10106 				gdk_window_get_size(w->window,
10107 #endif
10108 					vdata->xywh + 2, vdata->xywh + 3);
10109 				gdk_window_get_root_origin(gtk_widget_get_window(w),
10110 					vdata->xywh + 0, vdata->xywh + 1);
10111 			}
10112 			if (vdata->ininame && vdata->ininame[0])
10113 				rw_pos(vdata, TRUE);
10114 			/* Needed for "dialogparent" effect in KDE4 to not misbehave */
10115 			if (vdata->modal) gtk_window_set_modal(GTK_WINDOW(w), FALSE);
10116 			/* Needed in Windows to stop GTK+ lowering the main window,
10117 			 * and everywhere to avoid forcing focus to main window */
10118 			gtk_window_set_transient_for(GTK_WINDOW(w), NULL);
10119 		}
10120 	}
10121 	widget_showhide(wrap, state);
10122 	/* !!! Window must be visible, or maximize fails if either dimension is
10123 	 * already at max, with KDE3 & 4 at least - WJ */
10124 	if (mx) set_maximized(slot[0]);
10125 	if (ws) widget_showhide(ws, TRUE);
10126 	if (raise) gdk_window_raise(gtk_widget_get_window(GTK_WIDGET(slot[0])));
10127 	if (unfocus) gtk_window_set_focus(slot[0], NULL);
10128 	/* !!! Have to wait till canvas is displayed, to init keyboard */
10129 	if (keymap) keymap_reset(keymap[2]);
10130 }
10131 
cmd_set(void ** slot,int v)10132 void cmd_set(void **slot, int v)
10133 {
10134 	swdata *sd;
10135 	int op;
10136 
10137 	slot = origin_slot(slot);
10138 	op = GET_OP(slot);
10139 	if (IS_UNREAL(slot)) op = GET_UOP(slot);
10140 	sd = slot[0];
10141 // !!! Support only what actually used on, and their brethren
10142 	switch (op)
10143 	{
10144 	case op_DOCK:
10145 	{
10146 		dock_data *dd = slot[2];
10147 		GtkWidget *window, *vbox = dd->vbox, *pane = dd->pane;
10148 		char *ini = ((void **)slot[1])[1];
10149 		int w, w2;
10150 
10151 		if (!v ^ !!gtk_paned_get_child1(GTK_PANED(pane))) return; // nothing to do
10152 
10153 		window = gtk_widget_get_toplevel(slot[0]);
10154 #if GTK_MAJOR_VERSION == 3
10155 		if (gtk_widget_get_visible(window))
10156 			gtk_window_get_size(GTK_WINDOW(window), &w2, NULL);
10157 		/* Window size isn't yet valid */
10158 		else g_object_get(G_OBJECT(window), "default_width", &w2, NULL);
10159 #else
10160 		if (GTK_WIDGET_VISIBLE(window))
10161 			gdk_window_get_size(window->window, &w2, NULL);
10162 		/* Window size isn't yet valid */
10163 		else gtk_object_get(GTK_OBJECT(window), "default_width", &w2, NULL);
10164 #endif
10165 
10166 		if (v)
10167 		{
10168 			/* Restore dock size if set, autodetect otherwise */
10169 			w = inifile_get_gint32(ini, -1);
10170 			if (w >= 0) gtk_paned_set_position(GTK_PANED(pane), w2 - w);
10171 			/* Now, let's juggle the widgets */
10172 			gtk_widget_ref(vbox);
10173 			gtk_container_remove(GTK_CONTAINER(slot[0]), vbox);
10174 			gtk_paned_pack1(GTK_PANED(pane), vbox, TRUE, TRUE);
10175 			gtk_widget_show(pane);
10176 		}
10177 		else
10178 		{
10179 			inifile_set_gint32(ini, w2 - gtk_paned_get_position(
10180 				GTK_PANED(pane)));
10181 			gtk_widget_hide(pane);
10182 //			vbox = gtk_paned_get_child1(GTK_PANED(pane));
10183 			gtk_widget_ref(vbox);
10184 			gtk_container_remove(GTK_CONTAINER(pane), vbox);
10185 			xpack(slot[0], vbox);
10186 		}
10187 		gtk_widget_unref(vbox);
10188 		break;
10189 	}
10190 	case op_HVSPLIT:
10191 	{
10192 		hvsplit_data *hd = slot[2];
10193 		GtkWidget *pane, **p = hd->panes, *w = NULL, *box = slot[0];
10194 		int v0, v1;
10195 
10196 		v0 = GTK_WIDGET_VISIBLE(p[0]) ? 1 :
10197 			GTK_WIDGET_VISIBLE(p[1]) ? 2 : 0;
10198 		v1 = (int)v < 1 ? 0 : (int)v > 1 ? 2 : 1;
10199 		if (v1 == v0) break; // nothing to do
10200 		if (!v1) // hide 2nd part
10201 		{
10202 			pane = p[v0 - 1];
10203 			gtk_widget_hide(pane);
10204 			w = gtk_paned_get_child1(GTK_PANED(pane));
10205 			gtk_widget_ref(w);
10206 			gtk_container_remove(GTK_CONTAINER(pane), w);
10207 			xpack(box, w);
10208 		}
10209 		else if (!v0) // show 2nd part
10210 		{
10211 			pane = p[v1 - 1];
10212 			if (!gtk_paned_get_child2(GTK_PANED(pane))) // move
10213 			{
10214 				w = hd->inbox[1];
10215 				gtk_widget_ref(w);
10216 				gtk_container_remove(GTK_CONTAINER(
10217 					gtk_widget_get_parent(w)), w);
10218 				gtk_paned_pack2(GTK_PANED(pane), w, TRUE, TRUE);
10219 				gtk_widget_unref(w);
10220 				gtk_widget_show(w);
10221 			}
10222 			w = hd->inbox[0];
10223 			gtk_widget_ref(w);
10224 			gtk_container_remove(GTK_CONTAINER(box), w);
10225 			gtk_paned_pack1(GTK_PANED(pane), w, TRUE, TRUE);
10226 			gtk_widget_show(pane);
10227 		}
10228 		else // swap direction
10229 		{
10230 			pane = p[v0 - 1];
10231 			gtk_widget_hide(pane);
10232 			w = gtk_paned_get_child1(GTK_PANED(pane));
10233 			gtk_widget_ref(w);
10234 			gtk_container_remove(GTK_CONTAINER(pane), w);
10235 			gtk_paned_pack1(GTK_PANED(p[2 - v0]), w, TRUE, TRUE);
10236 			gtk_widget_unref(w);
10237 			w = gtk_paned_get_child2(GTK_PANED(pane));
10238 			gtk_widget_ref(w);
10239 			gtk_container_remove(GTK_CONTAINER(pane), w);
10240 			gtk_paned_pack2(GTK_PANED(p[2 - v0]), w, TRUE, TRUE);
10241 			gtk_widget_show(p[2 - v0]);
10242 		}
10243 		gtk_widget_unref(w);
10244 		break;
10245 	}
10246 	case op_NOSPIN:
10247 		spin_set_range(slot[0], v, v);
10248 		break;
10249 	case op_SPINSLIDE: case op_SPINSLIDEa:
10250 	case op_SPIN: case op_SPINc: case op_SPINa:
10251 		gtk_spin_button_set_value(slot[0], v);
10252 		break;
10253 	case op_uSCALE:
10254 		/* uSCALE ignores indirect writes after a direct one */
10255 		if (sd->cnt == 2) break;
10256 		sd->cnt &= 2;
10257 		// Fallthrough
10258 	case op_uSPIN: case op_uFSPIN: case op_uSPINa:
10259 	case op_uCHECK: case op_uCHECKb:
10260 		v = (op == op_uCHECK) || (op == op_uCHECKb) ? !!v :
10261 			v > sd->range[1] ? sd->range[1] :
10262 			v < sd->range[0] ? sd->range[0] : v;
10263 		if (v == sd->value) break;
10264 		// Fallthrough
10265 	case op_uCOLOR:
10266 		sd->value = v;
10267 		cmd_event(slot, op_EVT_CHANGE);
10268 		break;
10269 	case op_uOPT: case op_uOPTD: case op_uRPACK: case op_uRPACKD:
10270 		if ((v < 0) || (v >= sd->cnt) || !((char **)sd->strs)[v][0]) v = 0;
10271 		if (v == sd->value) break;
10272 		sd->value = v;
10273 		cmd_event(slot, op_EVT_SELECT);
10274 		break;
10275 	case op_BUTTON: case op_uBUTTON:
10276 		cmd_event(slot, op_EVT_CLICK); // for any value
10277 		break;
10278 	case op_TBBUTTON:
10279 #if GTK_MAJOR_VERSION == 3
10280 		g_signal_emit_by_name(slot[0], "clicked"); // for any value
10281 #else
10282 		gtk_button_clicked(slot[0]); // for any value
10283 #endif
10284 		break;
10285 	case op_uMENURITEM:
10286 		// Cannot unset itself, and no use to set twice
10287 		if (!v || sd->value) break;
10288 		/* Unset the whole group */
10289 		{
10290 			void **r = slot;
10291 			int n;
10292 
10293 			while ((n = ((swdata *)r[0])->range[0])) r -= n;
10294 			while (TRUE)
10295 			{
10296 				((swdata *)r[0])->value = FALSE;
10297 				if (!(n = ((swdata *)r[0])->range[1])) break;
10298 				r += n;
10299 			}
10300 		}
10301 		// Fallthrough
10302 	case op_uMENUCHECK:
10303 		v = !!v;
10304 		if (sd->value == v) break;
10305 		sd->value = v;
10306 		// Fallthrough
10307 	case op_uMENUITEM:
10308 		tbar_event(slot, op_EVT_CHANGE);
10309 		break;
10310 	case op_FSPIN:
10311 		gtk_spin_button_set_value(slot[0], v / 100.0);
10312 		break;
10313 	case op_TBTOGGLE: case op_TBRBUTTON:
10314 #if GTK_MAJOR_VERSION == 3 /* In GTK+1&2, same handler as for GtkToggleButton */
10315 		gtk_toggle_tool_button_set_active(slot[0], v);
10316 		break;
10317 #endif
10318 	case op_CHECK: case op_CHECKb: case op_TOGGLE:
10319 	case op_TBBOXTOG:
10320 		gtk_toggle_button_set_active(slot[0], v);
10321 		break;
10322 	case op_MENUITEM:
10323 		gtk_menu_item_activate(slot[0]); // for any value
10324 		break;
10325 	case op_MENUCHECK: case op_MENURITEM:
10326 		gtk_check_menu_item_set_active(slot[0], v);
10327 		break;
10328 	case op_OPT: case op_OPTD:
10329 		/* !!! No support for discontinuous lists, for now */
10330 		wj_option_menu_set_history(slot[0], v);
10331 		break;
10332 	case op_PCTCOMBO:
10333 	{
10334 		char buf[32];
10335 
10336 		sprintf(buf, "%d%%", v);
10337 		gtk_entry_set_text(GTK_ENTRY(pctcombo_entry(slot[0])), buf);
10338 #if GTK_MAJOR_VERSION == 1
10339 		/* Call the handler, for consistency */
10340 		get_evt_1(NULL, NEXT_SLOT(slot));
10341 #endif
10342 		break;
10343 	}
10344 #ifdef U_CPICK_MTPAINT
10345 	case op_HEXENTRY:
10346 		*(int *)slot_data(slot, GET_DDATA(wdata_slot(NEXT_SLOT(slot)))) = v;
10347 		set_hexentry(slot[0], v);
10348 		break;
10349 #endif
10350 	case op_PLAINBOOK:
10351 		gtk_notebook_set_page(slot[0], v);
10352 		break;
10353 	case op_LISTCCr:
10354 	{
10355 		listcc_data *dt = slot[2];
10356 		if ((v < 0) || (v >= *dt->cnt)) break; // Ensure sanity
10357 		*dt->idx = v;
10358 		listcc_select_item(slot);
10359 		break;
10360 	}
10361 	case op_uLISTCC: case op_uLISTC:
10362 	{
10363 		char *ddata = GET_DDATA(wdata_slot(slot));
10364 		int *cnt = (void *)(ddata + (int)GET_DESCV(slot, 2));
10365 
10366 		if ((v < 0) || (v >= *cnt)) break; // Ensure sanity
10367 		*(int *)(sd->strs) = v;
10368 		cmd_event(slot, op_EVT_SELECT);
10369 		break;
10370 	}
10371 	case op_LISTC: case op_LISTCd: case op_LISTCu:
10372 	case op_LISTCS: case op_LISTCX:
10373 		listc_select_index(slot[0], v);
10374 		break;
10375 	}
10376 }
10377 
10378 /* Passively query one slot, show where the result went */
cmd_read(void ** slot,void * ddata)10379 void *cmd_read(void **slot, void *ddata)
10380 {
10381 	return (do_query(ddata, origin_slot(slot), 3));
10382 }
10383 
cmd_peekv(void ** slot,void * res,int size,int idx)10384 void cmd_peekv(void **slot, void *res, int size, int idx)
10385 {
10386 // !!! Support only what actually used on
10387 	int op = GET_OP(slot);
10388 	if (size <= 0) return;
10389 	if (op == op_WDONE)
10390 	{
10391 		if (idx == WDATA_TABLET)
10392 		{
10393 			if (size >= sizeof(char *)) *(char **)res = tablet_device ?
10394 				(char *)gdk_device_get_name(tablet_device) : NULL;
10395 			return;
10396 		}
10397 		// skip to toplevel slot
10398 		slot = NEXT_SLOT(slot) , op = GET_OP(slot);
10399 	}
10400 	if (IS_UNREAL(slot)) op = GET_UOP(slot);
10401 	switch (op)
10402 	{
10403 	case op_uWINDOW:
10404 	case op_MAINWINDOW: case op_WINDOW: case op_WINDOWm: case op_DIALOGm:
10405 		if ((idx == WINDOW_DPI) && (size >= sizeof(int)))
10406 		{
10407 			GtkWidget *w = op == op_uWINDOW ? main_window : slot[0];
10408 			*(int *)res = cmd_mode ? 72 : // Use FreeType's default DPI
10409 				(int)(window_dpi(w) / window_scale(w) + 0.5);
10410 		}
10411 		break;
10412 	case op_FPICKpm: fpick_get_filename(slot[0], res, size, idx); break;
10413 	case op_uFPICK:
10414 	{
10415 		char *s = ((swdata *)slot[0])->strs;
10416 		/* !!! Pseudo widget uses system encoding in all modes */
10417 		if (idx == FPICK_RAW) s = strrchr(s, DIR_SEP) + 1; // Guaranteed
10418 		strncpy0(res, s, size);
10419 		break;
10420 	}
10421 	case op_PENTRY: case op_PATH: case op_PATHs:
10422 	{
10423 		char *s = (char *)gtk_entry_get_text(slot[0]);
10424 		if (idx == PATH_VALUE) gtkncpy(res, s, size);
10425 		else strncpy0(res, s, size); // PATH_RAW
10426 		break;
10427 	}
10428 	case op_CSCROLL:
10429 	{
10430 		GtkAdjustment *xa, *ya;
10431 		int *v = res;
10432 		get_scroll_adjustments(slot[0], &xa, &ya);
10433 		if (idx == CSCROLL_XYSIZE)
10434 		{
10435 			switch (size / sizeof(int))
10436 			{
10437 			default:
10438 			case 4: v[3] = gtk_adjustment_get_page_size(ya);
10439 			case 3: v[2] = gtk_adjustment_get_page_size(xa);
10440 			case 2: v[1] = gtk_adjustment_get_value(ya);
10441 			case 1: v[0] = gtk_adjustment_get_value(xa);
10442 			case 0: break;
10443 			}
10444 		}
10445 		else if (idx == CSCROLL_LIMITS)
10446 		{
10447 			if (size >= sizeof(int))
10448 				v[0] = gtk_adjustment_get_upper(xa) -
10449 					gtk_adjustment_get_page_size(xa);
10450 			if (size >= sizeof(int) * 2)
10451 				v[1] = gtk_adjustment_get_upper(ya) -
10452 					gtk_adjustment_get_page_size(ya);
10453 		}
10454 		break;
10455 	}
10456 	case op_CANVAS:
10457 	{
10458 		GtkWidget *w = slot[0];
10459 		if (idx == CANVAS_SIZE)
10460 		{
10461 			int *v = res;
10462 #if GTK_MAJOR_VERSION == 3
10463 			GtkAllocation alloc;
10464 			gtk_widget_get_allocation(w, &alloc);
10465 			if (size >= sizeof(int)) v[0] = alloc.width;
10466 			if (size >= sizeof(int) * 2) v[1] = alloc.height;
10467 #else
10468 			if (size >= sizeof(int)) v[0] = w->allocation.width;
10469 			if (size >= sizeof(int) * 2) v[1] = w->allocation.height;
10470 #endif
10471 		}
10472 		else if (idx == CANVAS_VPORT)
10473 		{
10474 			if (size < sizeof(int) * 4) break;
10475 			wjcanvas_get_vport(w, res);
10476 		}
10477 		else if (idx == CANVAS_FIND_MOUSE)
10478 		{
10479 			mouse_ext *m = res;
10480 			gint x, y;
10481 			int vport[4];
10482 
10483 			// No reason to parcel the data
10484 			if (size < sizeof(mouse_ext)) break;
10485 			gdk_window_get_pointer(gtk_widget_get_window(w), &x, &y,
10486 				&m->state);
10487 			wjcanvas_get_vport(w, vport);
10488 			m->x = x + vport[0];
10489 			m->y = y + vport[1];
10490 
10491 			m->button = state_to_button(m->state);
10492 			m->count = 0;
10493 			m->pressure = MAX_PRESSURE;
10494 		}
10495 		break;
10496 	}
10497 	case op_LISTC: case op_LISTCd: case op_LISTCu:
10498 	case op_LISTCS: case op_LISTCX:
10499 		/* if (idx == LISTC_ORDER) */
10500 		listc_get_order(slot[0], res, size / sizeof(int));
10501 		break;
10502 #if 0 /* Getting raw selection - not needed for now */
10503 		*(int *)res = (clist->selection ? (int)clist->selection->data : 0);
10504 #endif
10505 	case op_KEYMAP:
10506 		if (size >= sizeof(keymap_dd *))
10507 			*(keymap_dd **)res = keymap_export(slot[2]);
10508 		break;
10509 	}
10510 }
10511 
10512 /* These cannot be peeked just by calling do_query() with a preset int* var:
10513 	op_TBRBUTTON
10514 	op_MENURITEM
10515 	op_TCOLOR
10516 	op_COMBOENTRY
10517 	op_ENTRY, op_MLENTRY
10518 	op_TEXT
10519  * Others can, if need be */
10520 
10521 /* If window manager does not support iconifying, you can try to make do with
10522  * lowering mtPaint's windows instead, enabling NO_ICONIFY.
10523  * However, the windows do not then reappear back on their own if you switched
10524  * focus to something else (as in preparing that something for the delayed
10525  * screenshot being taken). And it only works with GTK+1 */
10526 //#define NO_ICONIFY
10527 
cmd_setv(void ** slot,void * res,int idx)10528 void cmd_setv(void **slot, void *res, int idx)
10529 {
10530 // !!! Support only what actually used on
10531 	int op = GET_OP(slot);
10532 	if (op == op_WDONE)
10533 	{
10534 		if (idx == WDATA_ACTMAP)
10535 		{
10536 			v_dd *vdata = GET_VDATA(slot);
10537 			if (vdata->actmask != (unsigned)res) // If anything changed
10538 				act_state(vdata, (unsigned)res);
10539 			return;
10540 		}
10541 		// skip to toplevel slot
10542 		slot = NEXT_SLOT(slot) , op = GET_OP(slot);
10543 	}
10544 	if (IS_UNREAL(slot)) op = GET_UOP(slot);
10545 	switch (op)
10546 	{
10547 	case op_uWINDOW:
10548 		if (cmd_mode || (idx != WINDOW_DISAPPEAR)) break;
10549 		// Fallthrough
10550 	case op_MAINWINDOW: case op_WINDOW: case op_WINDOWm: case op_DIALOGm:
10551 	{
10552 		v_dd *vdata = GET_VDATA(PREV_SLOT(slot));
10553 
10554 		if (idx == WINDOW_TITLE)
10555 			gtk_window_set_title(slot[0], res);
10556 		else if (idx == WINDOW_ESC_BTN)
10557 		{
10558 			GtkAccelGroup *ag = gtk_accel_group_new();
10559 			gtk_widget_add_accelerator(*(void **)res, "clicked", ag,
10560 				KEY(Escape), 0, (GtkAccelFlags)0);
10561 			gtk_window_add_accel_group(slot[0], ag);
10562 		}
10563 		else if (idx == WINDOW_FOCUS)
10564 		{
10565 			/* Cannot move focus to nowhere while window is hidden */
10566 			if (!res && !GTK_WIDGET_VISIBLE(slot[0]))
10567 				vdata->unfocus = TRUE;
10568 			gtk_window_set_focus(slot[0], res ? *(void **)res : NULL);
10569 		}
10570 		else if (idx == WINDOW_RAISE)
10571 		{
10572 			if (GTK_WIDGET_VISIBLE(slot[0]))
10573 				gdk_window_raise(gtk_widget_get_window(
10574 					GTK_WIDGET(slot[0])));
10575 			/* Cannot raise hidden window, will do it later */
10576 			else vdata->raise = TRUE;
10577 		}
10578 		else if (idx == WINDOW_DISAPPEAR)
10579 		{
10580 			GtkWidget *w = slot[0];
10581 
10582 			if (IS_UNREAL(slot)) w = NULL;
10583 			if (!res) /* Show again */
10584 			{
10585 				if (!w) break; // Paranoia
10586 #if (GTK_MAJOR_VERSION >= 2) || !defined(NO_ICONIFY)
10587 				set_iconify(w, FALSE);
10588 #endif
10589 				gdk_window_raise(gtk_widget_get_window(w));
10590 				break;
10591 			}
10592 			if (w == main_window) w = NULL;
10593 			/* Hide from view, to allow a screenshot */
10594 #if (GTK_MAJOR_VERSION == 1) && defined(NO_ICONIFY)
10595 			/* For gdk_window_lower() to clear the screen of all
10596 			 * mtPaint's windows, the window being lowered must be a
10597 			 * visible (mapped) transient. If none such is given, try
10598 			 * to find one; if not found, hope it means mainwindow
10599 			 * is the only one visible - WJ */
10600 			if (!w)
10601 			{
10602 				GList *top = gtk_container_get_toplevels();
10603 				for (; top; top = top->next)
10604 				{
10605 					GtkWidget *tw = top->data;
10606 					if (!tw || (tw == main_window) ||
10607 						!GTK_WIDGET_MAPPED(tw) ||
10608 						!GTK_WINDOW(tw)->transient_parent)
10609 						continue;
10610 					w = tw;
10611 					break;
10612 				}
10613 			}
10614 
10615 			gdk_window_lower(main_window->window);
10616 			if (w) gdk_window_lower(w->window);
10617 #else /* Iconify */
10618 			if (w)
10619 			{
10620 				gtk_window_set_transient_for(slot[0], NULL);
10621 				set_iconify(w, TRUE);
10622 			}
10623 			set_iconify(main_window, TRUE);
10624 #endif
10625 
10626 			gdk_flush();
10627 			handle_events(); 	// Wait for minimize
10628 
10629 #if GTK_MAJOR_VERSION == 1
10630 			sleep(1);	// Wait a second for screen to redraw
10631 #else /* #if GTK_MAJOR_VERSION >= 2 */
10632 			g_usleep(400000);	// Wait 0.4 s for screen to redraw
10633 #endif
10634 		}
10635 		else if (idx == WINDOW_TEXTENG) do_render_text(res);
10636 		break;
10637 	}
10638 	case op_FPICKpm: fpick_set_filename(slot[0], res, idx); break;
10639 	case op_uFPICK:
10640 	{
10641 		char *s, *ts, *s0 = ((swdata *)slot[0])->strs;
10642 
10643 		/* !!! Pseudo widget uses system encoding in all modes */
10644 		if (idx == FPICK_RAW)
10645 		{
10646 			ts = strrchr(s0, DIR_SEP); // Guaranteed to be there
10647 			s = wjstrcat(NULL, 0, s0, ts - s0 + 1, res, NULL);
10648 		}
10649 		else s = resolve_path(NULL, PATHBUF, res);
10650 
10651 		free(s0);
10652 		((swdata *)slot[0])->strs = s;
10653 		break;
10654 	}
10655 	case op_NBOOK: case op_NBOOKl:
10656 		gtk_notebook_set_show_tabs(slot[0], (int)res);
10657 		break;
10658 	case op_SPINSLIDE: case op_SPINSLIDEa:
10659 	case op_SPIN: case op_SPINc: case op_SPINa:
10660 	{
10661 		int *v = res, n = v[0];
10662 		spin_set_range(slot[0], v[1], v[2]);
10663 		gtk_spin_button_set_value(slot[0], n);
10664 		break;
10665 	}
10666 	case op_uSPIN: case op_uSPINa:
10667 	{
10668 		swdata *sd = slot[0];
10669 		int n, a, b, *v = res;
10670 
10671 		sd->range[0] = a = v[1];
10672 		sd->range[1] = b = v[2];
10673 		n = v[0] > b ? b : v[0] < a ? a : v[0];
10674 		if (n != sd->value)
10675 		{
10676 			sd->value = n;
10677 			cmd_event(slot, op_EVT_CHANGE);
10678 		}
10679 		break;
10680 	}
10681 	case op_MENUITEM: case op_MENUCHECK: case op_MENURITEM:
10682 		gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(slot[0]))), res);
10683 		break;
10684 	case op_LABEL: case op_WLABEL: case op_STLABEL:
10685 		gtk_label_set_text(slot[0], res);
10686 		break;
10687 	case op_TEXT:
10688 		set_textarea(slot[0], res);
10689 		break;
10690 	case op_ENTRY: case op_MLENTRY:
10691 		gtk_entry_set_text(slot[0], res);
10692 		break;
10693 	case op_uENTRY: case op_uPATHSTR:
10694 		set_uentry(slot[0], res);
10695 		cmd_event(slot, op_EVT_CHANGE);
10696 		break;
10697 	case op_PENTRY: case op_PATH: case op_PATHs:
10698 		set_path(slot[0], res, idx);
10699 		break;
10700 	case op_COMBOENTRY:
10701 		comboentry_set_text(slot[0], res);
10702 		break;
10703 	case op_COLOR: case op_TCOLOR:
10704 	{
10705 		int *v = res;
10706 		if (idx == COLOR_ALL)
10707 			cpick_set_colour_previous(slot[0], v[2], v[3]);
10708 		cpick_set_colour(slot[0], v[0], v[1]);
10709 		break;
10710 	}
10711 	case op_uCOLOR:
10712 		((swdata *)slot[0])->value = *(int *)res;
10713 		break;
10714 	case op_COLORLIST: case op_COLORLISTN:
10715 		colorlist_reset_color(slot, (int)res);
10716 		break;
10717 	case op_PROGRESS:
10718 #if GTK_MAJOR_VERSION == 3
10719 		gtk_progress_bar_set_fraction(slot[0], (int)res / 100.0);
10720 #else
10721 		gtk_progress_set_percentage(slot[0], (int)res / 100.0);
10722 #endif
10723 		break;
10724 	case op_CSCROLL:
10725 	{
10726 		/* if (idx == CSCROLL_XYRANGE) */
10727 		/* Used as a way to preset position for a delayed resize */
10728 		GtkAdjustment *xa, *ya;
10729 		int *v = res;
10730 		get_scroll_adjustments(slot[0], &xa, &ya);
10731 #if GTK_MAJOR_VERSION == 3
10732 		{
10733 			guint idv = g_signal_lookup("value_changed",
10734 				G_TYPE_FROM_INSTANCE(xa));
10735 			guint idc = g_signal_lookup("changed",
10736 				G_TYPE_FROM_INSTANCE(xa));
10737 			g_signal_handlers_block_matched(G_OBJECT(xa),
10738 				G_SIGNAL_MATCH_ID, idv, 0, NULL, NULL, NULL);
10739 			g_signal_handlers_block_matched(G_OBJECT(ya),
10740 				G_SIGNAL_MATCH_ID, idv, 0, NULL, NULL, NULL);
10741 			g_signal_handlers_block_matched(G_OBJECT(xa),
10742 				G_SIGNAL_MATCH_ID, idc, 0, NULL, NULL, NULL);
10743 			g_signal_handlers_block_matched(G_OBJECT(ya),
10744 				G_SIGNAL_MATCH_ID, idc, 0, NULL, NULL, NULL);
10745 			/* Set limit first and value after */
10746 			gtk_adjustment_set_upper(xa, v[2]);
10747 			gtk_adjustment_set_upper(ya, v[3]);
10748 			gtk_adjustment_set_value(xa, v[0]);
10749 			gtk_adjustment_set_value(ya, v[1]);
10750 			g_signal_handlers_unblock_matched(G_OBJECT(xa),
10751 				G_SIGNAL_MATCH_ID, idv, 0, NULL, NULL, NULL);
10752 			g_signal_handlers_unblock_matched(G_OBJECT(ya),
10753 				G_SIGNAL_MATCH_ID, idv, 0, NULL, NULL, NULL);
10754 			g_signal_handlers_unblock_matched(G_OBJECT(xa),
10755 				G_SIGNAL_MATCH_ID, idc, 0, NULL, NULL, NULL);
10756 			g_signal_handlers_unblock_matched(G_OBJECT(ya),
10757 				G_SIGNAL_MATCH_ID, idc, 0, NULL, NULL, NULL);
10758 		}
10759 #else
10760 		xa->value = v[0];
10761 		ya->value = v[1];
10762 		xa->upper = v[2];
10763 		ya->upper = v[3];
10764 #endif
10765 		break;
10766 	}
10767 	case op_CANVASIMG: case op_CANVASIMGB:
10768 	{
10769 		rgbimage_data *rd = slot[2];
10770 		int *v = res;
10771 		rd->w = v[0];
10772 		rd->h = v[1];
10773 		wjcanvas_size(slot[0], v[0], v[1]);
10774 		break;
10775 	}
10776 	case op_CANVAS:
10777 	{
10778 		GtkWidget *w = slot[0];
10779 		int vport[4], rxy[4], *v = res;
10780 
10781 		if (idx == CANVAS_SIZE)
10782 		{
10783 			wjcanvas_size(w, v[0], v[1]);
10784 			break;
10785 		}
10786 		wjcanvas_get_vport(w, vport);
10787 		if (idx == CANVAS_REPAINT)
10788 		{
10789 			wjcanvas_uncache(w, v);
10790 			if (clip(rxy, v[0], v[1], v[2], v[3], vport))
10791 				gtk_widget_queue_draw_area(w,
10792 					rxy[0] - vport[0], rxy[1] - vport[1],
10793 					rxy[2] - rxy[0], rxy[3] - rxy[1]);
10794 		}
10795 		else if (idx == CANVAS_PAINT)
10796 		{
10797 			rgbcontext *ctx = res;
10798 			/* Paint */
10799 			if (ctx->rgb)
10800 			{
10801 #if GTK_MAJOR_VERSION == 3
10802 				wjcanvas_draw_rgb(w, ctx->xy[0], ctx->xy[1],
10803 					ctx->xy[2] - ctx->xy[0],
10804 					ctx->xy[3] - ctx->xy[1],
10805 					ctx->rgb, (ctx->xy[2] - ctx->xy[0]) * 3,
10806 					0, TRUE);
10807 // !!! Remains to be seen if the below is needed - might be, in some cases
10808 //				gdk_window_process_updates(gtk_widget_get_window(w), FALSE);
10809 #else /* if GTK_MAJOR_VERSION <= 2 */
10810 				gdk_draw_rgb_image(w->window, w->style->black_gc,
10811 					ctx->xy[0] - vport[0],
10812 					ctx->xy[1] - vport[1],
10813 					ctx->xy[2] - ctx->xy[0],
10814 					ctx->xy[3] - ctx->xy[1],
10815 					GDK_RGB_DITHER_NONE, ctx->rgb,
10816 					(ctx->xy[2] - ctx->xy[0]) * 3);
10817 #endif
10818 				free(ctx->rgb);
10819 			}
10820 			/* Prepare */
10821 			else if (clip(ctx->xy, vport[0], vport[1],
10822 				vport[2], vport[3], ctx->xy))
10823 				ctx->rgb = malloc((ctx->xy[2] - ctx->xy[0]) *
10824 					(ctx->xy[3] - ctx->xy[1]) * 3);
10825 		}
10826 		else if (idx == CANVAS_BMOVE_MOUSE)
10827 		{
10828 			gint x, y;
10829 			gdk_window_get_pointer(gtk_widget_get_window(w), &x, &y, NULL);
10830 			if ((x >= 0) && (y >= 0) &&
10831 				((x += vport[0]) < vport[2]) &&
10832 				((y += vport[1]) < vport[3]) &&
10833 				/* Autoscroll canvas if required */
10834 				wjcanvas_scroll_in(w, x + v[0], y + v[1]))
10835 			{
10836 				wjcanvas_get_vport(w, rxy);
10837 				v[0] += vport[0] - rxy[0];
10838 				v[1] += vport[1] - rxy[1];
10839 			}
10840 			if (move_mouse_relative(v[0], v[1])) v[0] = v[1] = 0;
10841 		}
10842 		break;
10843 	}
10844 	case op_FCIMAGEP:
10845 	{
10846 		fcimage_data *fd = slot[2];
10847 		int *v = res;
10848 		if (!fd->xy) break;
10849 		memcpy(fd->xy, v, sizeof(int) * 2);
10850 		wjpixmap_move_cursor(slot[0], v[0], v[1]);
10851 		break;
10852 	}
10853 	case op_LISTCCr:
10854 		listcc_reset(slot, (int)res);
10855 // !!! May be needed if LISTCC_RESET_ROW w/GtkList gets used to display an added row
10856 //		gtk_widget_queue_resize(slot[0]);
10857 		break;
10858 	case op_LISTC: case op_LISTCd: case op_LISTCu:
10859 	case op_LISTCS: case op_LISTCX:
10860 		if (idx == LISTC_RESET_ROW)
10861 			listc_reset_row(slot[0], slot[2], (int)res);
10862 		else if (idx == LISTC_SORT)
10863 			listc_sort_by(slot[0], slot[2], (int)res);
10864 		break;
10865 #if 0 /* Moving raw selection - not needed for now */
10866 		gtk_clist_select_row(slot[0], (int)res, 0);
10867 #endif
10868 	case op_KEYMAP:
10869 		if (idx == KEYMAP_KEY) keymap_find(slot[2], res);
10870 		else /* if (idx == KEYMAP_MAP) */
10871 		{
10872 			keymap_init(slot[2], res);
10873 			keymap_reset(slot[2]);
10874 		}
10875 		break;
10876 	case op_EV_DRAGFROM:
10877 	{
10878 		drag_den *dp = (void *)slot;
10879 		if (idx == DRAG_ICON_RGB) dp->dc->color = (int)res;
10880 		else /* if (idx == DRAG_DATA) */
10881 		{
10882 			char **pp = res;
10883 			gtk_selection_data_set(dp->ds->data,
10884 				/* Could use "dp->ds->data->target" instead */
10885 				gdk_atom_intern(dp->d.format->target, FALSE),
10886 				dp->d.format->format ? dp->d.format->format : 8,
10887 				pp[0], pp[1] - pp[0]);
10888 		}
10889 		break;
10890 	}
10891 	case op_EV_COPY:
10892 	{
10893 		copy_den *cp = (void *)slot;
10894 		char **pp = res;
10895 		gtk_selection_data_set(cp->data, gtk_selection_data_get_target(cp->data),
10896 			cp->c.format->format ? cp->c.format->format : 8,
10897 			pp[0], pp[1] - pp[0]);
10898 		break;
10899 	}
10900 	case op_CLIPBOARD:
10901 		offer_text(slot, res);
10902 		break;
10903 #if GTK_MAJOR_VERSION >= 2
10904 	case op_FONTSEL:
10905 	{
10906 		GtkFontSelection *fs = slot[0];
10907 		fontsel_data *fd = slot[2];
10908 
10909 		fd->dpi = (int)res;
10910 		if (!fd->sysdpi) fd->sysdpi = window_dpi(main_window); // Init
10911 		/* To cause full preview reset */
10912 #if GTK_MAJOR_VERSION == 3
10913 		{
10914 			GtkWidget *e = gtk_font_selection_get_size_entry(fs);
10915 			char *s = strdup(gtk_entry_get_text(GTK_ENTRY(e)));
10916 
10917 			gtk_entry_set_text(GTK_ENTRY(e), "0");
10918 			gtk_widget_activate(e);
10919 			gtk_entry_set_text(GTK_ENTRY(e), s);
10920 			gtk_widget_activate(e);
10921 			free(s);
10922 			/* Force style update if not triggered by the above */
10923 			if (fd->dpi != fd->lastdpi) fontsel_style(
10924 				gtk_font_selection_get_preview_entry(fs), slot);
10925 		}
10926 #else /* #if GTK_MAJOR_VERSION <= 2 */
10927 		{
10928 			int size = fs->size;
10929 
10930 			fs->size = 0;
10931 			gtk_widget_activate(fs->size_entry);
10932 			fs->size = size;
10933 		}
10934 #endif
10935 		break;
10936 	}
10937 #endif
10938 	}
10939 }
10940 
cmd_repaint(void ** slot)10941 void cmd_repaint(void **slot)
10942 {
10943 	int op = op;
10944 	if (IS_UNREAL(slot)) return;
10945 #if GTK_MAJOR_VERSION == 3
10946 	op = GET_OP(slot);
10947 	/* Tell cached widgets to drop cache */
10948 	if (op == op_RGBIMAGE) reset_rgb(slot[0], slot[2]);
10949 	else if ((op == op_CANVASIMG) || (op == op_CANVASIMGB) || (op == op_CANVAS))
10950 		wjcanvas_uncache(slot[0], NULL);
10951 #endif
10952 #ifdef U_LISTS_GTK1
10953 	op = GET_OP(slot);
10954 	if ((op == op_COLORLIST) || (op == op_COLORLISTN))
10955 	/* Stupid GTK+ does nothing for gtk_widget_queue_draw(allcol_list) */
10956 		gtk_container_foreach(GTK_CONTAINER(slot[0]),
10957 			(GtkCallback)gtk_widget_queue_draw, NULL);
10958 	else
10959 #endif
10960 	gtk_widget_queue_draw(slot[0]);
10961 }
10962 
10963 #define SETCUR_KEY "mtPaint.cursor"
10964 
reset_cursor(GtkWidget * widget,gpointer user_data)10965 static void reset_cursor(GtkWidget *widget, gpointer user_data)
10966 {
10967 	gpointer c = gtk_object_get_data_by_id(GTK_OBJECT(widget), (GQuark)user_data);
10968 	if (c != (gpointer)(-1)) gdk_window_set_cursor(gtk_widget_get_window(widget), c);
10969 }
10970 
cmd_cursor(void ** slot,void ** cursor)10971 void cmd_cursor(void **slot, void **cursor)
10972 {
10973 	static GQuark setcur_key;
10974 	GtkWidget *w = slot[0];
10975 
10976 	if (IS_UNREAL(slot)) return;
10977 	/* Remember cursor for restoring it after realize */
10978 	if (!setcur_key) setcur_key = g_quark_from_static_string(SETCUR_KEY);
10979 	if (!gtk_object_get_data_by_id(slot[0], setcur_key))
10980 		gtk_signal_connect(slot[0], "realize",
10981 			GTK_SIGNAL_FUNC(reset_cursor), (gpointer)setcur_key);
10982 	gtk_object_set_data_by_id(slot[0], setcur_key, cursor ? cursor[0] :
10983 		(gpointer)(-1));
10984 
10985 	if (gtk_widget_get_window(w)) gdk_window_set_cursor(gtk_widget_get_window(w),
10986 		cursor ? cursor[0] : NULL);
10987 }
10988 
cmd_checkv(void ** slot,int idx)10989 int cmd_checkv(void **slot, int idx)
10990 {
10991 	int op = GET_OP(slot);
10992 	if (op == op_WDONE) // skip head noop
10993 		slot = NEXT_SLOT(slot) , op = GET_OP(slot);
10994 	if (IS_UNREAL(slot))
10995 	{
10996 		if (idx == SLOT_SENSITIVE)
10997 		{
10998 			/* Columns redirect to their list */
10999 			if ((op >= op_COLUMN_0) && (op < op_COLUMN_LAST))
11000 			{
11001 				col_data *c = ((swdata *)slot[0])->strs;
11002 				if (c) return (cmd_checkv(c->r, idx));
11003 			}
11004 			return (!((swdata *)slot[0])->insens);
11005 		}
11006 		if (idx == SLOT_UNREAL) return (TRUE);
11007 		op = GET_UOP(slot);
11008 	}
11009 	if (idx == SLOT_RADIO) return ((op == op_TBRBUTTON) ||
11010 		(op == op_MENURITEM) || (op == op_uMENURITEM));
11011 	if (op == op_CLIPBOARD)
11012 	{
11013 		if (idx == CLIP_OFFER) return (offer_clipboard(slot, FALSE));
11014 		if (idx == CLIP_PROCESS) return (process_clipboard(slot));
11015 	}
11016 	else if (op == op_EV_MOUSE)
11017 	{
11018 		mouse_den *dp = (void *)slot;
11019 		void **canvas = EV_PARENT(slot);
11020 		if (dp->mouse->count) return (FALSE); // Not a move
11021 		return (wjcanvas_bind_mouse(canvas[0], slot[0],
11022 			dp->mouse->x - dp->vport[0],
11023 			dp->mouse->y - dp->vport[1]));
11024 	}
11025 	else if (op < op_EVT_0) // Regular widget
11026 	{
11027 		if (idx == SLOT_SENSITIVE)
11028 			return (GTK_WIDGET_SENSITIVE(get_wrap(slot)));
11029 		if (idx == SLOT_FOCUSED)
11030 		{
11031 			GtkWidget *w = gtk_widget_get_toplevel(slot[0]);
11032 			if (!GTK_IS_WINDOW(w)) return (FALSE);
11033 			w = gtk_window_get_focus(GTK_WINDOW(w));
11034 			return (w && ((w == slot[0]) ||
11035 				gtk_widget_is_ancestor(w, slot[0])));
11036 		}
11037 		if (idx == SLOT_SCRIPTABLE)
11038 			return (!!(GET_OPF(slot) & WB_SFLAG));
11039 		/* if (idx == SLOT_UNREAL) return (FALSE); */
11040 	}
11041 	return (FALSE);
11042 }
11043 
cmd_event(void ** slot,int op)11044 void cmd_event(void **slot, int op)
11045 {
11046 	slot = op_slot(slot, op);
11047 	if (slot) get_evt_1(NULL, slot); // Found
11048 }
11049