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