]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkdlg.c
Stop using GtkDialog (for most purposes) in GTK 3!
[PuTTY.git] / unix / gtkdlg.c
1 /*
2  * gtkdlg.c - GTK implementation of the PuTTY configuration box.
3  */
4
5 #include <assert.h>
6 #include <stdarg.h>
7 #include <ctype.h>
8 #include <time.h>
9
10 #include <gtk/gtk.h>
11 #if !GTK_CHECK_VERSION(3,0,0)
12 #include <gdk/gdkkeysyms.h>
13 #endif
14
15 #define MAY_REFER_TO_GTK_IN_HEADERS
16
17 #include "putty.h"
18 #include "gtkcompat.h"
19 #include "gtkcols.h"
20 #include "gtkfont.h"
21 #include "gtkmisc.h"
22
23 #ifndef NOT_X_WINDOWS
24 #include <gdk/gdkx.h>
25 #include <X11/Xlib.h>
26 #include <X11/Xutil.h>
27 #endif
28
29 #ifdef TESTMODE
30 #define PUTTY_DO_GLOBALS               /* actually _define_ globals */
31 #endif
32
33 #include "storage.h"
34 #include "dialog.h"
35 #include "tree234.h"
36
37 #if GTK_CHECK_VERSION(2,0,0)
38 /* Decide which of GtkFileChooserDialog and GtkFileSelection to use */
39 #define USE_GTK_FILE_CHOOSER_DIALOG
40 #endif
41
42 struct Shortcut {
43     GtkWidget *widget;
44     struct uctrl *uc;
45     int action;
46 };
47
48 struct Shortcuts {
49     struct Shortcut sc[128];
50 };
51
52 struct uctrl {
53     union control *ctrl;
54     GtkWidget *toplevel;
55     GtkWidget **buttons; int nbuttons; /* for radio buttons */
56     GtkWidget *entry;         /* for editbox, filesel, fontsel */
57     GtkWidget *button;        /* for filesel, fontsel */
58 #if !GTK_CHECK_VERSION(2,4,0)
59     GtkWidget *list;          /* for listbox (in GTK1), combobox (<=GTK2.3) */
60     GtkWidget *menu;          /* for optionmenu (==droplist) */
61     GtkWidget *optmenu;       /* also for optionmenu */
62 #else
63     GtkWidget *combo;         /* for combo box (either editable or not) */
64 #endif
65 #if GTK_CHECK_VERSION(2,0,0)
66     GtkWidget *treeview;      /* for listbox (GTK2), droplist+combo (>=2.4) */
67     GtkListStore *listmodel;  /* for all types of list box */
68 #endif
69     GtkWidget *text;          /* for text */
70     GtkWidget *label;         /* for dlg_label_change */
71     GtkAdjustment *adj;       /* for the scrollbar in a list box */
72     guint entrysig;
73     guint textsig;
74     int nclicks;
75 };
76
77 struct dlgparam {
78     tree234 *byctrl, *bywidget;
79     void *data;
80     struct { unsigned char r, g, b, ok; } coloursel_result;   /* 0-255 */
81     /* `flags' are set to indicate when a GTK signal handler is being called
82      * due to automatic processing and should not flag a user event. */
83     int flags;
84     struct Shortcuts *shortcuts;
85     GtkWidget *window, *cancelbutton;
86     union control *currfocus, *lastfocus;
87 #if !GTK_CHECK_VERSION(2,0,0)
88     GtkWidget *currtreeitem, **treeitems;
89     int ntreeitems;
90 #else
91     int nselparams;
92     struct selparam *selparams;
93 #endif
94     int retval;
95 };
96 #define FLAG_UPDATING_COMBO_LIST 1
97 #define FLAG_UPDATING_LISTBOX    2
98
99 enum {                                 /* values for Shortcut.action */
100     SHORTCUT_EMPTY,                    /* no shortcut on this key */
101     SHORTCUT_TREE,                     /* focus a tree item */
102     SHORTCUT_FOCUS,                    /* focus the supplied widget */
103     SHORTCUT_UCTRL,                    /* do something sane with uctrl */
104     SHORTCUT_UCTRL_UP,                 /* uctrl is a draglist, move Up */
105     SHORTCUT_UCTRL_DOWN,               /* uctrl is a draglist, move Down */
106 };
107
108 #if GTK_CHECK_VERSION(2,0,0)
109 enum {
110     TREESTORE_PATH,
111     TREESTORE_PARAMS,
112     TREESTORE_NUM
113 };
114 #endif
115
116 /*
117  * Forward references.
118  */
119 static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
120                              gpointer data);
121 static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
122                          int chr, int action, void *ptr);
123 static void shortcut_highlight(GtkWidget *label, int chr);
124 #if !GTK_CHECK_VERSION(2,0,0)
125 static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
126                                     gpointer data);
127 static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
128                                    gpointer data);
129 static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
130                                       gpointer data);
131 static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
132                                         gpointer data);
133 #endif
134 #if !GTK_CHECK_VERSION(2,4,0)
135 static void menuitem_activate(GtkMenuItem *item, gpointer data);
136 #endif
137 #if GTK_CHECK_VERSION(3,0,0)
138 static void colourchoose_response(GtkDialog *dialog,
139                                   gint response_id, gpointer data);
140 #else
141 static void coloursel_ok(GtkButton *button, gpointer data);
142 static void coloursel_cancel(GtkButton *button, gpointer data);
143 #endif
144 static void window_destroy(GtkWidget *widget, gpointer data);
145 int get_listitemheight(GtkWidget *widget);
146
147 static int uctrl_cmp_byctrl(void *av, void *bv)
148 {
149     struct uctrl *a = (struct uctrl *)av;
150     struct uctrl *b = (struct uctrl *)bv;
151     if (a->ctrl < b->ctrl)
152         return -1;
153     else if (a->ctrl > b->ctrl)
154         return +1;
155     return 0;
156 }
157
158 static int uctrl_cmp_byctrl_find(void *av, void *bv)
159 {
160     union control *a = (union control *)av;
161     struct uctrl *b = (struct uctrl *)bv;
162     if (a < b->ctrl)
163         return -1;
164     else if (a > b->ctrl)
165         return +1;
166     return 0;
167 }
168
169 static int uctrl_cmp_bywidget(void *av, void *bv)
170 {
171     struct uctrl *a = (struct uctrl *)av;
172     struct uctrl *b = (struct uctrl *)bv;
173     if (a->toplevel < b->toplevel)
174         return -1;
175     else if (a->toplevel > b->toplevel)
176         return +1;
177     return 0;
178 }
179
180 static int uctrl_cmp_bywidget_find(void *av, void *bv)
181 {
182     GtkWidget *a = (GtkWidget *)av;
183     struct uctrl *b = (struct uctrl *)bv;
184     if (a < b->toplevel)
185         return -1;
186     else if (a > b->toplevel)
187         return +1;
188     return 0;
189 }
190
191 static void dlg_init(struct dlgparam *dp)
192 {
193     dp->byctrl = newtree234(uctrl_cmp_byctrl);
194     dp->bywidget = newtree234(uctrl_cmp_bywidget);
195     dp->coloursel_result.ok = FALSE;
196     dp->window = dp->cancelbutton = NULL;
197 #if !GTK_CHECK_VERSION(2,0,0)
198     dp->treeitems = NULL;
199     dp->currtreeitem = NULL;
200 #endif
201     dp->flags = 0;
202     dp->currfocus = NULL;
203 }
204
205 static void dlg_cleanup(struct dlgparam *dp)
206 {
207     struct uctrl *uc;
208
209     freetree234(dp->byctrl);           /* doesn't free the uctrls inside */
210     dp->byctrl = NULL;
211     while ( (uc = index234(dp->bywidget, 0)) != NULL) {
212         del234(dp->bywidget, uc);
213         sfree(uc->buttons);
214         sfree(uc);
215     }
216     freetree234(dp->bywidget);
217     dp->bywidget = NULL;
218 #if !GTK_CHECK_VERSION(2,0,0)
219     sfree(dp->treeitems);
220 #endif
221 }
222
223 static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc)
224 {
225     add234(dp->byctrl, uc);
226     add234(dp->bywidget, uc);
227 }
228
229 static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, union control *ctrl)
230 {
231     if (!dp->byctrl)
232         return NULL;
233     return find234(dp->byctrl, ctrl, uctrl_cmp_byctrl_find);
234 }
235
236 static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w)
237 {
238     struct uctrl *ret = NULL;
239     if (!dp->bywidget)
240         return NULL;
241     do {
242         ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find);
243         if (ret)
244             return ret;
245         w = gtk_widget_get_parent(w);
246     } while (w);
247     return ret;
248 }
249
250 union control *dlg_last_focused(union control *ctrl, void *dlg)
251 {
252     struct dlgparam *dp = (struct dlgparam *)dlg;
253     if (dp->currfocus != ctrl)
254         return dp->currfocus;
255     else
256         return dp->lastfocus;
257 }
258
259 void dlg_radiobutton_set(union control *ctrl, void *dlg, int which)
260 {
261     struct dlgparam *dp = (struct dlgparam *)dlg;
262     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
263     assert(uc->ctrl->generic.type == CTRL_RADIO);
264     assert(uc->buttons != NULL);
265     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), TRUE);
266 }
267
268 int dlg_radiobutton_get(union control *ctrl, void *dlg)
269 {
270     struct dlgparam *dp = (struct dlgparam *)dlg;
271     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
272     int i;
273
274     assert(uc->ctrl->generic.type == CTRL_RADIO);
275     assert(uc->buttons != NULL);
276     for (i = 0; i < uc->nbuttons; i++)
277         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->buttons[i])))
278             return i;
279     return 0;                          /* got to return something */
280 }
281
282 void dlg_checkbox_set(union control *ctrl, void *dlg, int checked)
283 {
284     struct dlgparam *dp = (struct dlgparam *)dlg;
285     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
286     assert(uc->ctrl->generic.type == CTRL_CHECKBOX);
287     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked);
288 }
289
290 int dlg_checkbox_get(union control *ctrl, void *dlg)
291 {
292     struct dlgparam *dp = (struct dlgparam *)dlg;
293     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
294     assert(uc->ctrl->generic.type == CTRL_CHECKBOX);
295     return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel));
296 }
297
298 void dlg_editbox_set(union control *ctrl, void *dlg, char const *text)
299 {
300     struct dlgparam *dp = (struct dlgparam *)dlg;
301     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
302     GtkWidget *entry;
303     char *tmpstring;
304     assert(uc->ctrl->generic.type == CTRL_EDITBOX);
305
306 #if GTK_CHECK_VERSION(2,4,0)
307     if (uc->combo)
308         entry = gtk_bin_get_child(GTK_BIN(uc->combo));
309     else
310 #endif
311     entry = uc->entry;
312
313     assert(entry != NULL);
314
315     /*
316      * GTK 2 implements gtk_entry_set_text by means of two separate
317      * operations: first delete the previous text leaving the empty
318      * string, then insert the new text. This causes two calls to
319      * the "changed" signal.
320      *
321      * The first call to "changed", if allowed to proceed normally,
322      * will cause an EVENT_VALCHANGE event on the edit box, causing
323      * a call to dlg_editbox_get() which will read the empty string
324      * out of the GtkEntry - and promptly write it straight into the
325      * Conf structure, which is precisely where our `text' pointer
326      * is probably pointing, so the second editing operation will
327      * insert that instead of the string we originally asked for.
328      *
329      * Hence, we must take our own copy of the text before we do
330      * this.
331      */
332     tmpstring = dupstr(text);
333     gtk_entry_set_text(GTK_ENTRY(entry), tmpstring);
334     sfree(tmpstring);
335 }
336
337 char *dlg_editbox_get(union control *ctrl, void *dlg)
338 {
339     struct dlgparam *dp = (struct dlgparam *)dlg;
340     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
341     assert(uc->ctrl->generic.type == CTRL_EDITBOX);
342
343 #if GTK_CHECK_VERSION(2,4,0)
344     if (uc->combo) {
345         return dupstr(gtk_entry_get_text
346                       (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))));
347     }
348 #endif
349
350     if (uc->entry) {
351         return dupstr(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
352     }
353
354     assert(!"We shouldn't get here");
355 }
356
357 #if !GTK_CHECK_VERSION(2,4,0)
358 static void container_remove_and_destroy(GtkWidget *w, gpointer data)
359 {
360     GtkContainer *cont = GTK_CONTAINER(data);
361     /* gtk_container_remove will unref the widget for us; we need not. */
362     gtk_container_remove(cont, w);
363 }
364 #endif
365
366 /* The `listbox' functions can also apply to combo boxes. */
367 void dlg_listbox_clear(union control *ctrl, void *dlg)
368 {
369     struct dlgparam *dp = (struct dlgparam *)dlg;
370     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
371
372     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
373            uc->ctrl->generic.type == CTRL_LISTBOX);
374
375 #if !GTK_CHECK_VERSION(2,4,0)
376     if (uc->menu) {
377         gtk_container_foreach(GTK_CONTAINER(uc->menu),
378                               container_remove_and_destroy,
379                               GTK_CONTAINER(uc->menu));
380         return;
381     }
382     if (uc->list) {
383         gtk_list_clear_items(GTK_LIST(uc->list), 0, -1);
384         return;
385     }
386 #endif
387 #if GTK_CHECK_VERSION(2,0,0)
388     if (uc->listmodel) {
389         gtk_list_store_clear(uc->listmodel);
390         return;
391     }
392 #endif
393     assert(!"We shouldn't get here");
394 }
395
396 void dlg_listbox_del(union control *ctrl, void *dlg, int index)
397 {
398     struct dlgparam *dp = (struct dlgparam *)dlg;
399     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
400
401     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
402            uc->ctrl->generic.type == CTRL_LISTBOX);
403
404 #if !GTK_CHECK_VERSION(2,4,0)
405     if (uc->menu) {
406         gtk_container_remove
407             (GTK_CONTAINER(uc->menu),
408              g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index));
409         return;
410     }
411     if (uc->list) {
412         gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
413         return;
414     }
415 #endif
416 #if GTK_CHECK_VERSION(2,0,0)
417     if (uc->listmodel) {
418         GtkTreePath *path;
419         GtkTreeIter iter;
420         assert(uc->listmodel != NULL);
421         path = gtk_tree_path_new_from_indices(index, -1);
422         gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
423         gtk_list_store_remove(uc->listmodel, &iter);
424         gtk_tree_path_free(path);
425         return;
426     }
427 #endif
428     assert(!"We shouldn't get here");
429 }
430
431 void dlg_listbox_add(union control *ctrl, void *dlg, char const *text)
432 {
433     dlg_listbox_addwithid(ctrl, dlg, text, 0);
434 }
435
436 /*
437  * Each listbox entry may have a numeric id associated with it.
438  * Note that some front ends only permit a string to be stored at
439  * each position, which means that _if_ you put two identical
440  * strings in any listbox then you MUST not assign them different
441  * IDs and expect to get meaningful results back.
442  */
443 void dlg_listbox_addwithid(union control *ctrl, void *dlg,
444                            char const *text, int id)
445 {
446     struct dlgparam *dp = (struct dlgparam *)dlg;
447     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
448
449     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
450            uc->ctrl->generic.type == CTRL_LISTBOX);
451
452     /*
453      * This routine is long and complicated in both GTK 1 and 2,
454      * and completely different. Sigh.
455      */
456     dp->flags |= FLAG_UPDATING_COMBO_LIST;
457
458 #if !GTK_CHECK_VERSION(2,4,0)
459     if (uc->menu) {
460         /*
461          * List item in a drop-down (but non-combo) list. Tabs are
462          * ignored; we just provide a standard menu item with the
463          * text.
464          */
465         GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
466
467         gtk_container_add(GTK_CONTAINER(uc->menu), menuitem);
468         gtk_widget_show(menuitem);
469
470         g_object_set_data(G_OBJECT(menuitem), "user-data",
471                           GINT_TO_POINTER(id));
472         g_signal_connect(G_OBJECT(menuitem), "activate",
473                          G_CALLBACK(menuitem_activate), dp);
474         goto done;
475     }
476     if (uc->list && uc->entry) {
477         /*
478          * List item in a combo-box list, which means the sensible
479          * thing to do is make it a perfectly normal label. Hence
480          * tabs are disregarded.
481          */
482         GtkWidget *listitem = gtk_list_item_new_with_label(text);
483
484         gtk_container_add(GTK_CONTAINER(uc->list), listitem);
485         gtk_widget_show(listitem);
486
487         g_object_set_data(G_OBJECT(listitem), "user-data",
488                           GINT_TO_POINTER(id));
489         goto done;
490     }
491 #endif
492 #if !GTK_CHECK_VERSION(2,0,0)
493     if (uc->list) {
494         /*
495          * List item in a non-combo-box list box. We make all of
496          * these Columns containing GtkLabels. This allows us to do
497          * the nasty force_left hack irrespective of whether there
498          * are tabs in the thing.
499          */
500         GtkWidget *listitem = gtk_list_item_new();
501         GtkWidget *cols = columns_new(10);
502         gint *percents;
503         int i, ncols;
504
505         /* Count the tabs in the text, and hence determine # of columns. */
506         ncols = 1;
507         for (i = 0; text[i]; i++)
508             if (text[i] == '\t')
509                 ncols++;
510
511         assert(ncols <=
512                (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1));
513         percents = snewn(ncols, gint);
514         percents[ncols-1] = 100;
515         for (i = 0; i < ncols-1; i++) {
516             percents[i] = uc->ctrl->listbox.percentages[i];
517             percents[ncols-1] -= percents[i];
518         }
519         columns_set_cols(COLUMNS(cols), ncols, percents);
520         sfree(percents);
521
522         for (i = 0; i < ncols; i++) {
523             int len = strcspn(text, "\t");
524             char *dup = dupprintf("%.*s", len, text);
525             GtkWidget *label;
526
527             text += len;
528             if (*text) text++;
529             label = gtk_label_new(dup);
530             sfree(dup);
531
532             columns_add(COLUMNS(cols), label, i, 1);
533             columns_force_left_align(COLUMNS(cols), label);
534             gtk_widget_show(label);
535         }
536         gtk_container_add(GTK_CONTAINER(listitem), cols);
537         gtk_widget_show(cols);
538         gtk_container_add(GTK_CONTAINER(uc->list), listitem);
539         gtk_widget_show(listitem);
540
541         if (ctrl->listbox.multisel) {
542             g_signal_connect(G_OBJECT(listitem), "key_press_event",
543                              G_CALLBACK(listitem_multi_key), uc->adj);
544         } else {
545             g_signal_connect(G_OBJECT(listitem), "key_press_event",
546                              G_CALLBACK(listitem_single_key), uc->adj);
547         }
548         g_signal_connect(G_OBJECT(listitem), "focus_in_event",
549                          G_CALLBACK(widget_focus), dp);
550         g_signal_connect(G_OBJECT(listitem), "button_press_event",
551                          G_CALLBACK(listitem_button_press), dp);
552         g_signal_connect(G_OBJECT(listitem), "button_release_event",
553                          G_CALLBACK(listitem_button_release), dp);
554         g_object_set_data(G_OBJECT(listitem), "user-data",
555                           GINT_TO_POINTER(id));
556         goto done;
557     }
558 #else
559     if (uc->listmodel) {
560         GtkTreeIter iter;
561         int i, cols;
562
563         dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update */
564         gtk_list_store_append(uc->listmodel, &iter);
565         dp->flags &= ~FLAG_UPDATING_LISTBOX;
566         gtk_list_store_set(uc->listmodel, &iter, 0, id, -1);
567
568         /*
569          * Now go through text and divide it into columns at the tabs,
570          * as necessary.
571          */
572         cols = (uc->ctrl->generic.type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1);
573         cols = cols ? cols : 1;
574         for (i = 0; i < cols; i++) {
575             int collen = strcspn(text, "\t");
576             char *tmpstr = snewn(collen+1, char);
577             memcpy(tmpstr, text, collen);
578             tmpstr[collen] = '\0';
579             gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1);
580             sfree(tmpstr);
581             text += collen;
582             if (*text) text++;
583         }
584         goto done;
585     }
586 #endif
587     assert(!"We shouldn't get here");
588     done:
589     dp->flags &= ~FLAG_UPDATING_COMBO_LIST;
590 }
591
592 int dlg_listbox_getid(union control *ctrl, void *dlg, int index)
593 {
594     struct dlgparam *dp = (struct dlgparam *)dlg;
595     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
596
597     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
598            uc->ctrl->generic.type == CTRL_LISTBOX);
599
600 #if !GTK_CHECK_VERSION(2,4,0)
601     if (uc->menu || uc->list) {
602         GList *children;
603         GObject *item;
604
605         children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
606                                                         uc->list));
607         item = G_OBJECT(g_list_nth_data(children, index));
608         g_list_free(children);
609
610         return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "user-data"));
611     }
612 #endif
613 #if GTK_CHECK_VERSION(2,0,0)
614     if (uc->listmodel) {
615         GtkTreePath *path;
616         GtkTreeIter iter;
617         int ret;
618
619         path = gtk_tree_path_new_from_indices(index, -1);
620         gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
621         gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1);
622         gtk_tree_path_free(path);
623
624         return ret;
625     }
626 #endif
627     assert(!"We shouldn't get here");
628     return -1;                         /* placate dataflow analysis */
629 }
630
631 /* dlg_listbox_index returns <0 if no single element is selected. */
632 int dlg_listbox_index(union control *ctrl, void *dlg)
633 {
634     struct dlgparam *dp = (struct dlgparam *)dlg;
635     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
636
637     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
638            uc->ctrl->generic.type == CTRL_LISTBOX);
639
640 #if !GTK_CHECK_VERSION(2,4,0)
641     if (uc->menu || uc->list) {
642         GList *children;
643         GtkWidget *item, *activeitem;
644         int i;
645         int selected = -1;
646
647         if (uc->menu)
648             activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
649         else
650             activeitem = NULL;         /* unnecessarily placate gcc */
651
652         children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
653                                                         uc->list));
654         for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL;
655              i++, children = children->next) {
656             if (uc->menu ? activeitem == item :
657                 GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) {
658                 if (selected == -1)
659                     selected = i;
660                 else
661                     selected = -2;
662             }
663         }
664         g_list_free(children);
665         return selected < 0 ? -1 : selected;
666     }
667 #else
668     if (uc->combo) {
669         /*
670          * This API function already does the right thing in the
671          * case of no current selection.
672          */
673         return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo));
674     }
675 #endif
676 #if GTK_CHECK_VERSION(2,0,0)
677     if (uc->treeview) {
678         GtkTreeSelection *treesel;
679         GtkTreePath *path;
680         GtkTreeModel *model;
681         GList *sellist;
682         gint *indices;
683         int ret;
684
685         assert(uc->treeview != NULL);
686         treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
687
688         if (gtk_tree_selection_count_selected_rows(treesel) != 1)
689             return -1;
690
691         sellist = gtk_tree_selection_get_selected_rows(treesel, &model);
692
693         assert(sellist && sellist->data);
694         path = sellist->data;
695
696         if (gtk_tree_path_get_depth(path) != 1) {
697             ret = -1;
698         } else {
699             indices = gtk_tree_path_get_indices(path);
700             if (!indices) {
701                 ret = -1;
702             } else {
703                 ret = indices[0];
704             }
705         }
706
707         g_list_foreach(sellist, (GFunc)gtk_tree_path_free, NULL);
708         g_list_free(sellist);
709
710         return ret;
711     }
712 #endif
713     assert(!"We shouldn't get here");
714     return -1;                         /* placate dataflow analysis */
715 }
716
717 int dlg_listbox_issel(union control *ctrl, void *dlg, int index)
718 {
719     struct dlgparam *dp = (struct dlgparam *)dlg;
720     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
721
722     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
723            uc->ctrl->generic.type == CTRL_LISTBOX);
724
725 #if !GTK_CHECK_VERSION(2,4,0)
726     if (uc->menu || uc->list) {
727         GList *children;
728         GtkWidget *item, *activeitem;
729
730         assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
731                uc->ctrl->generic.type == CTRL_LISTBOX);
732         assert(uc->menu != NULL || uc->list != NULL);
733
734         children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
735                                                         uc->list));
736         item = GTK_WIDGET(g_list_nth_data(children, index));
737         g_list_free(children);
738
739         if (uc->menu) {
740             activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
741             return item == activeitem;
742         } else {
743             return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED;
744         }
745     }
746 #else
747     if (uc->combo) {
748         /*
749          * This API function already does the right thing in the
750          * case of no current selection.
751          */
752         return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)) == index;
753     }
754 #endif
755 #if GTK_CHECK_VERSION(2,0,0)
756     if (uc->treeview) {
757         GtkTreeSelection *treesel;
758         GtkTreePath *path;
759         int ret;
760
761         assert(uc->treeview != NULL);
762         treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
763
764         path = gtk_tree_path_new_from_indices(index, -1);
765         ret = gtk_tree_selection_path_is_selected(treesel, path);
766         gtk_tree_path_free(path);
767
768         return ret;
769     }
770 #endif
771     assert(!"We shouldn't get here");
772     return -1;                         /* placate dataflow analysis */
773 }
774
775 void dlg_listbox_select(union control *ctrl, void *dlg, int index)
776 {
777     struct dlgparam *dp = (struct dlgparam *)dlg;
778     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
779
780     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
781            uc->ctrl->generic.type == CTRL_LISTBOX);
782
783 #if !GTK_CHECK_VERSION(2,4,0)
784     if (uc->optmenu) {
785         gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index);
786         return;
787     } 
788     if (uc->list) {
789         int nitems;
790         GList *items;
791         gdouble newtop, newbot;
792
793         gtk_list_select_item(GTK_LIST(uc->list), index);
794
795         /*
796          * Scroll the list box if necessary to ensure the newly
797          * selected item is visible.
798          */
799         items = gtk_container_children(GTK_CONTAINER(uc->list));
800         nitems = g_list_length(items);
801         if (nitems > 0) {
802             int modified = FALSE;
803             g_list_free(items);
804             newtop = uc->adj->lower +
805                 (uc->adj->upper - uc->adj->lower) * index / nitems;
806             newbot = uc->adj->lower +
807                 (uc->adj->upper - uc->adj->lower) * (index+1) / nitems;
808             if (uc->adj->value > newtop) {
809                 modified = TRUE;
810                 uc->adj->value = newtop;
811             } else if (uc->adj->value < newbot - uc->adj->page_size) {
812                 modified = TRUE;
813                 uc->adj->value = newbot - uc->adj->page_size;
814             }
815             if (modified)
816                 gtk_adjustment_value_changed(uc->adj);
817         }
818         return;
819     }
820 #else
821     if (uc->combo) {
822         gtk_combo_box_set_active(GTK_COMBO_BOX(uc->combo), index);
823         return;
824     }
825 #endif
826 #if GTK_CHECK_VERSION(2,0,0)
827     if (uc->treeview) {
828         GtkTreeSelection *treesel;
829         GtkTreePath *path;
830
831         treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
832
833         path = gtk_tree_path_new_from_indices(index, -1);
834         gtk_tree_selection_select_path(treesel, path);
835         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->treeview),
836                                      path, NULL, FALSE, 0.0, 0.0);
837         gtk_tree_path_free(path);
838         return;
839     }
840 #endif
841     assert(!"We shouldn't get here");
842 }
843
844 void dlg_text_set(union control *ctrl, void *dlg, char const *text)
845 {
846     struct dlgparam *dp = (struct dlgparam *)dlg;
847     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
848
849     assert(uc->ctrl->generic.type == CTRL_TEXT);
850     assert(uc->text != NULL);
851
852     gtk_label_set_text(GTK_LABEL(uc->text), text);
853 }
854
855 void dlg_label_change(union control *ctrl, void *dlg, char const *text)
856 {
857     struct dlgparam *dp = (struct dlgparam *)dlg;
858     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
859
860     switch (uc->ctrl->generic.type) {
861       case CTRL_BUTTON:
862         gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
863         shortcut_highlight(uc->toplevel, ctrl->button.shortcut);
864         break;
865       case CTRL_CHECKBOX:
866         gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
867         shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut);
868         break;
869       case CTRL_RADIO:
870         gtk_label_set_text(GTK_LABEL(uc->label), text);
871         shortcut_highlight(uc->label, ctrl->radio.shortcut);
872         break;
873       case CTRL_EDITBOX:
874         gtk_label_set_text(GTK_LABEL(uc->label), text);
875         shortcut_highlight(uc->label, ctrl->editbox.shortcut);
876         break;
877       case CTRL_FILESELECT:
878         gtk_label_set_text(GTK_LABEL(uc->label), text);
879         shortcut_highlight(uc->label, ctrl->fileselect.shortcut);
880         break;
881       case CTRL_FONTSELECT:
882         gtk_label_set_text(GTK_LABEL(uc->label), text);
883         shortcut_highlight(uc->label, ctrl->fontselect.shortcut);
884         break;
885       case CTRL_LISTBOX:
886         gtk_label_set_text(GTK_LABEL(uc->label), text);
887         shortcut_highlight(uc->label, ctrl->listbox.shortcut);
888         break;
889       default:
890         assert(!"This shouldn't happen");
891         break;
892     }
893 }
894
895 void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn)
896 {
897     struct dlgparam *dp = (struct dlgparam *)dlg;
898     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
899     /* We must copy fn->path before passing it to gtk_entry_set_text.
900      * See comment in dlg_editbox_set() for the reasons. */
901     char *duppath = dupstr(fn->path);
902     assert(uc->ctrl->generic.type == CTRL_FILESELECT);
903     assert(uc->entry != NULL);
904     gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath);
905     sfree(duppath);
906 }
907
908 Filename *dlg_filesel_get(union control *ctrl, void *dlg)
909 {
910     struct dlgparam *dp = (struct dlgparam *)dlg;
911     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
912     assert(uc->ctrl->generic.type == CTRL_FILESELECT);
913     assert(uc->entry != NULL);
914     return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
915 }
916
917 void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fs)
918 {
919     struct dlgparam *dp = (struct dlgparam *)dlg;
920     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
921     /* We must copy fs->name before passing it to gtk_entry_set_text.
922      * See comment in dlg_editbox_set() for the reasons. */
923     char *dupname = dupstr(fs->name);
924     assert(uc->ctrl->generic.type == CTRL_FONTSELECT);
925     assert(uc->entry != NULL);
926     gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname);
927     sfree(dupname);
928 }
929
930 FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg)
931 {
932     struct dlgparam *dp = (struct dlgparam *)dlg;
933     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
934     assert(uc->ctrl->generic.type == CTRL_FONTSELECT);
935     assert(uc->entry != NULL);
936     return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
937 }
938
939 /*
940  * Bracketing a large set of updates in these two functions will
941  * cause the front end (if possible) to delay updating the screen
942  * until it's all complete, thus avoiding flicker.
943  */
944 void dlg_update_start(union control *ctrl, void *dlg)
945 {
946     /*
947      * Apparently we can't do this at all in GTK. GtkCList supports
948      * freeze and thaw, but not GtkList. Bah.
949      */
950 }
951
952 void dlg_update_done(union control *ctrl, void *dlg)
953 {
954     /*
955      * Apparently we can't do this at all in GTK. GtkCList supports
956      * freeze and thaw, but not GtkList. Bah.
957      */
958 }
959
960 void dlg_set_focus(union control *ctrl, void *dlg)
961 {
962     struct dlgparam *dp = (struct dlgparam *)dlg;
963     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
964
965     switch (ctrl->generic.type) {
966       case CTRL_CHECKBOX:
967       case CTRL_BUTTON:
968         /* Check boxes and buttons get the focus _and_ get toggled. */
969         gtk_widget_grab_focus(uc->toplevel);
970         break;
971       case CTRL_FILESELECT:
972       case CTRL_FONTSELECT:
973       case CTRL_EDITBOX:
974         if (uc->entry) {
975             /* Anything containing an edit box gets that focused. */
976             gtk_widget_grab_focus(uc->entry);
977         }
978 #if GTK_CHECK_VERSION(2,4,0)
979         else if (uc->combo) {
980             /* Failing that, there'll be a combo box. */
981             gtk_widget_grab_focus(uc->combo);
982         }
983 #endif
984         break;
985       case CTRL_RADIO:
986         /*
987          * Radio buttons: we find the currently selected button and
988          * focus it.
989          */
990         {
991             int i;
992             for (i = 0; i < ctrl->radio.nbuttons; i++)
993                 if (gtk_toggle_button_get_active
994                     (GTK_TOGGLE_BUTTON(uc->buttons[i]))) {
995                     gtk_widget_grab_focus(uc->buttons[i]);
996                 }
997         }
998         break;
999       case CTRL_LISTBOX:
1000 #if !GTK_CHECK_VERSION(2,4,0)
1001         if (uc->optmenu) {
1002             gtk_widget_grab_focus(uc->optmenu);
1003             break;
1004         }
1005 #else
1006         if (uc->combo) {
1007             gtk_widget_grab_focus(uc->combo);
1008             break;
1009         }
1010 #endif
1011 #if !GTK_CHECK_VERSION(2,0,0)
1012         if (uc->list) {
1013             /*
1014              * For GTK-1 style list boxes, we tell it to focus one
1015              * of its children, which appears to do the Right
1016              * Thing.
1017              */
1018             gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD);
1019             break;
1020         }
1021 #else
1022         if (uc->treeview) {
1023             gtk_widget_grab_focus(uc->treeview);
1024             break;
1025         }
1026 #endif
1027         assert(!"We shouldn't get here");
1028         break;
1029     }
1030 }
1031
1032 /*
1033  * During event processing, you might well want to give an error
1034  * indication to the user. dlg_beep() is a quick and easy generic
1035  * error; dlg_error() puts up a message-box or equivalent.
1036  */
1037 void dlg_beep(void *dlg)
1038 {
1039     gdk_beep();
1040 }
1041
1042 #if !GTK_CHECK_VERSION(3,0,0)
1043 static void errmsg_button_clicked(GtkButton *button, gpointer data)
1044 {
1045     gtk_widget_destroy(GTK_WIDGET(data));
1046 }
1047 #endif
1048
1049 static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child)
1050 {
1051 #if !GTK_CHECK_VERSION(2,0,0)
1052     gint x, y, w, h, dx, dy;
1053     GtkRequisition req;
1054     gtk_window_set_position(GTK_WINDOW(child), GTK_WIN_POS_NONE);
1055     gtk_widget_size_request(GTK_WIDGET(child), &req);
1056
1057     gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(parent)), &x, &y);
1058     gdk_window_get_size(gtk_widget_get_window(GTK_WIDGET(parent)), &w, &h);
1059
1060     /*
1061      * One corner of the transient will be offset inwards, by 1/4
1062      * of the parent window's size, from the corresponding corner
1063      * of the parent window. The corner will be chosen so as to
1064      * place the transient closer to the centre of the screen; this
1065      * should avoid transients going off the edge of the screen on
1066      * a regular basis.
1067      */
1068     if (x + w/2 < gdk_screen_width() / 2)
1069         dx = x + w/4;                  /* work from left edges */
1070     else
1071         dx = x + 3*w/4 - req.width;    /* work from right edges */
1072     if (y + h/2 < gdk_screen_height() / 2)
1073         dy = y + h/4;                  /* work from top edges */
1074     else
1075         dy = y + 3*h/4 - req.height;   /* work from bottom edges */
1076     gtk_widget_set_uposition(GTK_WIDGET(child), dx, dy);
1077 #endif
1078 }
1079
1080 void align_label_left(GtkLabel *label)
1081 {
1082 #if GTK_CHECK_VERSION(3,16,0)
1083     gtk_label_set_xalign(label, 0.0);
1084 #else
1085     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1086 #endif
1087 }
1088
1089 void dlg_error_msg(void *dlg, const char *msg)
1090 {
1091     struct dlgparam *dp = (struct dlgparam *)dlg;
1092     GtkWidget *window;
1093
1094 #if GTK_CHECK_VERSION(3,0,0)
1095     window = gtk_message_dialog_new(GTK_WINDOW(dp->window),
1096                                     (GTK_DIALOG_MODAL |
1097                                      GTK_DIALOG_DESTROY_WITH_PARENT),
1098                                     GTK_MESSAGE_ERROR,
1099                                     GTK_BUTTONS_CLOSE,
1100                                     "%s", msg);
1101     gtk_dialog_run(GTK_DIALOG(window));
1102     gtk_widget_destroy(window);
1103 #else
1104     GtkWidget *hbox, *text, *ok;
1105
1106     window = gtk_dialog_new();
1107     text = gtk_label_new(msg);
1108     align_label_left(GTK_LABEL(text));
1109     hbox = gtk_hbox_new(FALSE, 0);
1110     gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20);
1111     gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(window))),
1112                        hbox, FALSE, FALSE, 20);
1113     gtk_widget_show(text);
1114     gtk_widget_show(hbox);
1115     gtk_window_set_title(GTK_WINDOW(window), "Error");
1116     gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
1117     ok = gtk_button_new_with_label("OK");
1118     gtk_box_pack_end(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(window))),
1119                      ok, FALSE, FALSE, 0);
1120     gtk_widget_show(ok);
1121     gtk_widget_set_can_default(ok, TRUE);
1122     gtk_window_set_default(GTK_WINDOW(window), ok);
1123     g_signal_connect(G_OBJECT(ok), "clicked",
1124                      G_CALLBACK(errmsg_button_clicked), window);
1125     g_signal_connect(G_OBJECT(window), "destroy",
1126                      G_CALLBACK(window_destroy), NULL);
1127     gtk_window_set_modal(GTK_WINDOW(window), TRUE);
1128     gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(dp->window));
1129     set_transient_window_pos(dp->window, window);
1130     gtk_widget_show(window);
1131     gtk_main();
1132 #endif
1133
1134     post_main();
1135 }
1136
1137 /*
1138  * This function signals to the front end that the dialog's
1139  * processing is completed, and passes an integer value (typically
1140  * a success status).
1141  */
1142 void dlg_end(void *dlg, int value)
1143 {
1144     struct dlgparam *dp = (struct dlgparam *)dlg;
1145     dp->retval = value;
1146     gtk_widget_destroy(dp->window);
1147 }
1148
1149 void dlg_refresh(union control *ctrl, void *dlg)
1150 {
1151     struct dlgparam *dp = (struct dlgparam *)dlg;
1152     struct uctrl *uc;
1153
1154     if (ctrl) {
1155         if (ctrl->generic.handler != NULL)
1156             ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
1157     } else {
1158         int i;
1159
1160         for (i = 0; (uc = index234(dp->byctrl, i)) != NULL; i++) {
1161             assert(uc->ctrl != NULL);
1162             if (uc->ctrl->generic.handler != NULL)
1163                 uc->ctrl->generic.handler(uc->ctrl, dp,
1164                                           dp->data, EVENT_REFRESH);
1165         }
1166     }
1167 }
1168
1169 void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b)
1170 {
1171     struct dlgparam *dp = (struct dlgparam *)dlg;
1172     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
1173
1174 #if GTK_CHECK_VERSION(3,0,0)
1175     GtkWidget *coloursel =
1176         gtk_color_chooser_dialog_new("Select a colour",
1177                                      GTK_WINDOW(dp->window));
1178     gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(coloursel), FALSE);
1179 #else
1180     GtkWidget *okbutton, *cancelbutton;
1181     GtkWidget *coloursel =
1182         gtk_color_selection_dialog_new("Select a colour");
1183     GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel);
1184     GtkColorSelection *cs = GTK_COLOR_SELECTION
1185         (gtk_color_selection_dialog_get_color_selection(ccs));
1186     gtk_color_selection_set_has_opacity_control(cs, FALSE);
1187 #endif
1188
1189     dp->coloursel_result.ok = FALSE;
1190
1191     gtk_window_set_modal(GTK_WINDOW(coloursel), TRUE);
1192
1193 #if GTK_CHECK_VERSION(3,0,0)
1194     {
1195         GdkRGBA rgba;
1196         rgba.red = r / 255.0;
1197         rgba.green = g / 255.0;
1198         rgba.blue = b / 255.0;
1199         rgba.alpha = 1.0;              /* fully opaque! */
1200         gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(coloursel), &rgba);
1201     }
1202 #elif GTK_CHECK_VERSION(2,0,0)
1203     {
1204         GdkColor col;
1205         col.red = r * 0x0101;
1206         col.green = g * 0x0101;
1207         col.blue = b * 0x0101;
1208         gtk_color_selection_set_current_color(cs, &col);
1209     }
1210 #else
1211     {
1212         gdouble cvals[4];
1213         cvals[0] = r / 255.0;
1214         cvals[1] = g / 255.0;
1215         cvals[2] = b / 255.0;
1216         cvals[3] = 1.0;                /* fully opaque! */
1217         gtk_color_selection_set_color(cs, cvals);
1218     }
1219 #endif
1220
1221     g_object_set_data(G_OBJECT(coloursel), "user-data", (gpointer)uc);
1222
1223 #if GTK_CHECK_VERSION(3,0,0)
1224     g_signal_connect(G_OBJECT(coloursel), "response",
1225                      G_CALLBACK(colourchoose_response), (gpointer)dp);
1226 #else
1227
1228 #if GTK_CHECK_VERSION(2,0,0)
1229     g_object_get(G_OBJECT(ccs),
1230                  "ok-button", &okbutton,
1231                  "cancel-button", &cancelbutton,
1232                  (const char *)NULL);
1233 #else
1234     okbutton = ccs->ok_button;
1235     cancelbutton = ccs->cancel_button;
1236 #endif
1237     g_object_set_data(G_OBJECT(okbutton), "user-data",
1238                         (gpointer)coloursel);
1239     g_object_set_data(G_OBJECT(cancelbutton), "user-data",
1240                         (gpointer)coloursel);
1241     g_signal_connect(G_OBJECT(okbutton), "clicked",
1242                      G_CALLBACK(coloursel_ok), (gpointer)dp);
1243     g_signal_connect(G_OBJECT(cancelbutton), "clicked",
1244                      G_CALLBACK(coloursel_cancel), (gpointer)dp);
1245     g_signal_connect_swapped(G_OBJECT(okbutton), "clicked",
1246                              G_CALLBACK(gtk_widget_destroy),
1247                              (gpointer)coloursel);
1248     g_signal_connect_swapped(G_OBJECT(cancelbutton), "clicked",
1249                              G_CALLBACK(gtk_widget_destroy),
1250                              (gpointer)coloursel);
1251 #endif
1252     gtk_widget_show(coloursel);
1253 }
1254
1255 int dlg_coloursel_results(union control *ctrl, void *dlg,
1256                           int *r, int *g, int *b)
1257 {
1258     struct dlgparam *dp = (struct dlgparam *)dlg;
1259     if (dp->coloursel_result.ok) {
1260         *r = dp->coloursel_result.r;
1261         *g = dp->coloursel_result.g;
1262         *b = dp->coloursel_result.b;
1263         return 1;
1264     } else
1265         return 0;
1266 }
1267
1268 /* ----------------------------------------------------------------------
1269  * Signal handlers while the dialog box is active.
1270  */
1271
1272 static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
1273                              gpointer data)
1274 {
1275     struct dlgparam *dp = (struct dlgparam *)data;
1276     struct uctrl *uc = dlg_find_bywidget(dp, widget);
1277     union control *focus;
1278
1279     if (uc && uc->ctrl)
1280         focus = uc->ctrl;
1281     else
1282         focus = NULL;
1283
1284     if (focus != dp->currfocus) {
1285         dp->lastfocus = dp->currfocus;
1286         dp->currfocus = focus;
1287     }
1288
1289     return FALSE;
1290 }
1291
1292 static void button_clicked(GtkButton *button, gpointer data)
1293 {
1294     struct dlgparam *dp = (struct dlgparam *)data;
1295     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
1296     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
1297 }
1298
1299 static void button_toggled(GtkToggleButton *tb, gpointer data)
1300 {
1301     struct dlgparam *dp = (struct dlgparam *)data;
1302     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tb));
1303     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
1304 }
1305
1306 static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event,
1307                             gpointer data)
1308 {
1309     /*
1310      * GtkEntry has a nasty habit of eating the Return key, which
1311      * is unhelpful since it doesn't actually _do_ anything with it
1312      * (it calls gtk_widget_activate, but our edit boxes never need
1313      * activating). So I catch Return before GtkEntry sees it, and
1314      * pass it straight on to the parent widget. Effect: hitting
1315      * Return in an edit box will now activate the default button
1316      * in the dialog just like it will everywhere else.
1317      */
1318     GtkWidget *parent = gtk_widget_get_parent(widget);
1319     if (event->keyval == GDK_KEY_Return && parent != NULL) {
1320         gboolean return_val;
1321         g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
1322         g_signal_emit_by_name(G_OBJECT(parent), "key_press_event",
1323                               event, &return_val);
1324         return return_val;
1325     }
1326     return FALSE;
1327 }
1328
1329 static void editbox_changed(GtkEditable *ed, gpointer data)
1330 {
1331     struct dlgparam *dp = (struct dlgparam *)data;
1332     if (!(dp->flags & FLAG_UPDATING_COMBO_LIST)) {
1333         struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
1334         uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
1335     }
1336 }
1337
1338 static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event,
1339                                   gpointer data)
1340 {
1341     struct dlgparam *dp = (struct dlgparam *)data;
1342     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
1343     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_REFRESH);
1344     return FALSE;
1345 }
1346
1347 #if !GTK_CHECK_VERSION(2,0,0)
1348
1349 /*
1350  * GTK 1 list box event handlers.
1351  */
1352
1353 static gboolean listitem_key(GtkWidget *item, GdkEventKey *event,
1354                              gpointer data, int multiple)
1355 {
1356     GtkAdjustment *adj = GTK_ADJUSTMENT(data);
1357
1358     if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
1359         event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
1360         event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
1361         event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down) {
1362         /*
1363          * Up, Down, PgUp or PgDn have been pressed on a ListItem
1364          * in a list box. So, if the list box is single-selection:
1365          * 
1366          *  - if the list item in question isn't already selected,
1367          *    we simply select it.
1368          *  - otherwise, we find the next one (or next
1369          *    however-far-away) in whichever direction we're going,
1370          *    and select that.
1371          *     + in this case, we must also fiddle with the
1372          *       scrollbar to ensure the newly selected item is
1373          *       actually visible.
1374          * 
1375          * If it's multiple-selection, we do all of the above
1376          * except actually selecting anything, so we move the focus
1377          * and fiddle the scrollbar to follow it.
1378          */
1379         GtkWidget *list = item->parent;
1380
1381         g_signal_stop_emission_by_name(G_OBJECT(item), "key_press_event");
1382
1383         if (!multiple &&
1384             GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) {
1385                 gtk_list_select_child(GTK_LIST(list), item);
1386         } else {
1387             int direction =
1388                 (event->keyval==GDK_Up || event->keyval==GDK_KP_Up ||
1389                  event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
1390                 ? -1 : +1;
1391             int step =
1392                 (event->keyval==GDK_Page_Down || 
1393                  event->keyval==GDK_KP_Page_Down ||
1394                  event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
1395                 ? 2 : 1;
1396             int i, n;
1397             GList *children, *chead;
1398
1399             chead = children = gtk_container_children(GTK_CONTAINER(list));
1400
1401             n = g_list_length(children);
1402
1403             if (step == 2) {
1404                 /*
1405                  * Figure out how many list items to a screenful,
1406                  * and adjust the step appropriately.
1407                  */
1408                 step = 0.5 + adj->page_size * n / (adj->upper - adj->lower);
1409                 step--;                /* go by one less than that */
1410             }
1411
1412             i = 0;
1413             while (children != NULL) {
1414                 if (item == children->data)
1415                     break;
1416                 children = children->next;
1417                 i++;
1418             }
1419
1420             while (step > 0) {
1421                 if (direction < 0 && i > 0)
1422                     children = children->prev, i--;
1423                 else if (direction > 0 && i < n-1)
1424                     children = children->next, i++;
1425                 step--;
1426             }
1427
1428             if (children && children->data) {
1429                 if (!multiple)
1430                     gtk_list_select_child(GTK_LIST(list),
1431                                           GTK_WIDGET(children->data));
1432                 gtk_widget_grab_focus(GTK_WIDGET(children->data));
1433                 gtk_adjustment_clamp_page
1434                     (adj,
1435                      adj->lower + (adj->upper-adj->lower) * i / n,
1436                      adj->lower + (adj->upper-adj->lower) * (i+1) / n);
1437             }
1438
1439             g_list_free(chead);
1440         }
1441         return TRUE;
1442     }
1443
1444     return FALSE;
1445 }
1446
1447 static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
1448                                     gpointer data)
1449 {
1450     return listitem_key(item, event, data, FALSE);
1451 }
1452
1453 static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
1454                                    gpointer data)
1455 {
1456     return listitem_key(item, event, data, TRUE);
1457 }
1458
1459 static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
1460                                       gpointer data)
1461 {
1462     struct dlgparam *dp = (struct dlgparam *)data;
1463     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
1464     switch (event->type) {
1465     default:
1466     case GDK_BUTTON_PRESS: uc->nclicks = 1; break;
1467     case GDK_2BUTTON_PRESS: uc->nclicks = 2; break;
1468     case GDK_3BUTTON_PRESS: uc->nclicks = 3; break;
1469     }
1470     return FALSE;
1471 }
1472
1473 static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
1474                                         gpointer data)
1475 {
1476     struct dlgparam *dp = (struct dlgparam *)data;
1477     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
1478     if (uc->nclicks>1) {
1479         uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
1480         return TRUE;
1481     }
1482     return FALSE;
1483 }
1484
1485 static void list_selchange(GtkList *list, gpointer data)
1486 {
1487     struct dlgparam *dp = (struct dlgparam *)data;
1488     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list));
1489     if (!uc) return;
1490     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
1491 }
1492
1493 static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction)
1494 {
1495     int index = dlg_listbox_index(uc->ctrl, dp);
1496     GList *children = gtk_container_children(GTK_CONTAINER(uc->list));
1497     GtkWidget *child;
1498
1499     if ((index < 0) ||
1500         (index == 0 && direction < 0) ||
1501         (index == g_list_length(children)-1 && direction > 0)) {
1502         gdk_beep();
1503         return;
1504     }
1505
1506     child = g_list_nth_data(children, index);
1507     gtk_widget_ref(child);
1508     gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
1509     g_list_free(children);
1510
1511     children = NULL;
1512     children = g_list_append(children, child);
1513     gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction);
1514     gtk_list_select_item(GTK_LIST(uc->list), index + direction);
1515     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
1516 }
1517
1518 static void draglist_up(GtkButton *button, gpointer data)
1519 {
1520     struct dlgparam *dp = (struct dlgparam *)data;
1521     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
1522     draglist_move(dp, uc, -1);
1523 }
1524
1525 static void draglist_down(GtkButton *button, gpointer data)
1526 {
1527     struct dlgparam *dp = (struct dlgparam *)data;
1528     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
1529     draglist_move(dp, uc, +1);
1530 }
1531
1532 #else /* !GTK_CHECK_VERSION(2,0,0) */
1533
1534 /*
1535  * GTK 2 list box event handlers.
1536  */
1537
1538 static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path,
1539                                 GtkTreeViewColumn *column, gpointer data)
1540 {
1541     struct dlgparam *dp = (struct dlgparam *)data;
1542     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(treeview));
1543     if (uc)
1544         uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
1545 }
1546
1547 static void listbox_selchange(GtkTreeSelection *treeselection,
1548                               gpointer data)
1549 {
1550     struct dlgparam *dp = (struct dlgparam *)data;
1551     GtkTreeView *tree = gtk_tree_selection_get_tree_view(treeselection);
1552     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
1553     if (uc)
1554         uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
1555 }
1556
1557 struct draglist_valchange_ctx {
1558     struct uctrl *uc;
1559     struct dlgparam *dp;
1560 };
1561
1562 static gboolean draglist_valchange(gpointer data)
1563 {
1564     struct draglist_valchange_ctx *ctx =
1565         (struct draglist_valchange_ctx *)data;
1566
1567     ctx->uc->ctrl->generic.handler(ctx->uc->ctrl, ctx->dp,
1568                                    ctx->dp->data, EVENT_VALCHANGE);
1569
1570     sfree(ctx);
1571
1572     return FALSE;
1573 }
1574
1575 static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path,
1576                             GtkTreeIter *iter, gpointer data)
1577 {
1578     struct dlgparam *dp = (struct dlgparam *)data;
1579     gpointer tree;
1580     struct uctrl *uc;
1581
1582     if (dp->flags & FLAG_UPDATING_LISTBOX)
1583         return;                        /* not a user drag operation */
1584
1585     tree = g_object_get_data(G_OBJECT(treemodel), "user-data");
1586     uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
1587     if (uc) {
1588         /*
1589          * We should cause EVENT_VALCHANGE on the list box, now
1590          * that its rows have been reordered. However, the GTK 2
1591          * docs say that at the point this signal is received the
1592          * new row might not have actually been filled in yet.
1593          *
1594          * (So what smegging use is it then, eh? Don't suppose it
1595          * occurred to you at any point that letting the
1596          * application know _after_ the reordering was compelete
1597          * might be helpful to someone?)
1598          *
1599          * To get round this, I schedule an idle function, which I
1600          * hope won't be called until the main event loop is
1601          * re-entered after the drag-and-drop handler has finished
1602          * furtling with the list store.
1603          */
1604         struct draglist_valchange_ctx *ctx =
1605             snew(struct draglist_valchange_ctx);
1606         ctx->uc = uc;
1607         ctx->dp = dp;
1608         g_idle_add(draglist_valchange, ctx);
1609     }
1610 }
1611
1612 #endif /* !GTK_CHECK_VERSION(2,0,0) */
1613
1614 #if !GTK_CHECK_VERSION(2,4,0)
1615
1616 static void menuitem_activate(GtkMenuItem *item, gpointer data)
1617 {
1618     struct dlgparam *dp = (struct dlgparam *)data;
1619     GtkWidget *menushell = GTK_WIDGET(item)->parent;
1620     gpointer optmenu = g_object_get_data(G_OBJECT(menushell), "user-data");
1621     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu));
1622     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
1623 }
1624
1625 #else
1626
1627 static void droplist_selchange(GtkComboBox *combo, gpointer data)
1628 {
1629     struct dlgparam *dp = (struct dlgparam *)data;
1630     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo));
1631     if (uc)
1632         uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
1633 }
1634
1635 #endif /* !GTK_CHECK_VERSION(2,4,0) */
1636
1637 #ifdef USE_GTK_FILE_CHOOSER_DIALOG
1638 static void filechoose_response(GtkDialog *dialog, gint response,
1639                                 gpointer data)
1640 {
1641     /* struct dlgparam *dp = (struct dlgparam *)data; */
1642     struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
1643     if (response == GTK_RESPONSE_ACCEPT) {
1644         gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1645     gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
1646         g_free(name);
1647     }
1648     gtk_widget_destroy(GTK_WIDGET(dialog));
1649 }
1650 #else
1651 static void filesel_ok(GtkButton *button, gpointer data)
1652 {
1653     /* struct dlgparam *dp = (struct dlgparam *)data; */
1654     gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data");
1655     struct uctrl *uc = g_object_get_data(G_OBJECT(filesel), "user-data");
1656     const char *name = gtk_file_selection_get_filename
1657         (GTK_FILE_SELECTION(filesel));
1658     gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
1659 }
1660 #endif
1661
1662 static void fontsel_ok(GtkButton *button, gpointer data)
1663 {
1664     /* struct dlgparam *dp = (struct dlgparam *)data; */
1665
1666 #if !GTK_CHECK_VERSION(2,0,0)
1667
1668     gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data");
1669     struct uctrl *uc = g_object_get_data(G_OBJECT(fontsel), "user-data");
1670     const char *name = gtk_font_selection_dialog_get_font_name
1671         (GTK_FONT_SELECTION_DIALOG(fontsel));
1672     gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
1673
1674 #else
1675
1676     unifontsel *fontsel = (unifontsel *)g_object_get_data
1677         (G_OBJECT(button), "user-data");
1678     struct uctrl *uc = (struct uctrl *)fontsel->user_data;
1679     char *name = unifontsel_get_name(fontsel);
1680     assert(name);              /* should always be ok after OK pressed */
1681     gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
1682     sfree(name);
1683
1684 #endif
1685 }
1686
1687 #if GTK_CHECK_VERSION(3,0,0)
1688
1689 static void colourchoose_response(GtkDialog *dialog,
1690                                   gint response_id, gpointer data)
1691 {
1692     struct dlgparam *dp = (struct dlgparam *)data;
1693     struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
1694
1695     if (response_id == GTK_RESPONSE_OK) {
1696         GdkRGBA rgba;
1697         gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &rgba);
1698         dp->coloursel_result.r = (int) (255 * rgba.red);
1699         dp->coloursel_result.g = (int) (255 * rgba.green);
1700         dp->coloursel_result.b = (int) (255 * rgba.blue);
1701         dp->coloursel_result.ok = TRUE;
1702     } else {
1703         dp->coloursel_result.ok = FALSE;
1704     }
1705
1706     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
1707
1708     gtk_widget_destroy(GTK_WIDGET(dialog));
1709 }
1710
1711 #else /* GTK 1/2 coloursel response handlers */
1712
1713 static void coloursel_ok(GtkButton *button, gpointer data)
1714 {
1715     struct dlgparam *dp = (struct dlgparam *)data;
1716     gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
1717     struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
1718
1719 #if GTK_CHECK_VERSION(2,0,0)
1720     {
1721         GtkColorSelection *cs = GTK_COLOR_SELECTION
1722             (gtk_color_selection_dialog_get_color_selection
1723              (GTK_COLOR_SELECTION_DIALOG(coloursel)));
1724         GdkColor col;
1725         gtk_color_selection_get_current_color(cs, &col);
1726         dp->coloursel_result.r = col.red / 0x0100;
1727         dp->coloursel_result.g = col.green / 0x0100;
1728         dp->coloursel_result.b = col.blue / 0x0100;
1729     }
1730 #else
1731     {
1732         GtkColorSelection *cs = GTK_COLOR_SELECTION
1733             (gtk_color_selection_dialog_get_color_selection
1734              (GTK_COLOR_SELECTION_DIALOG(coloursel)));
1735         gdouble cvals[4];
1736         gtk_color_selection_get_color(cs, cvals);
1737         dp->coloursel_result.r = (int) (255 * cvals[0]);
1738         dp->coloursel_result.g = (int) (255 * cvals[1]);
1739         dp->coloursel_result.b = (int) (255 * cvals[2]);
1740     }
1741 #endif
1742     dp->coloursel_result.ok = TRUE;
1743     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
1744 }
1745
1746 static void coloursel_cancel(GtkButton *button, gpointer data)
1747 {
1748     struct dlgparam *dp = (struct dlgparam *)data;
1749     gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
1750     struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
1751     dp->coloursel_result.ok = FALSE;
1752     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
1753 }
1754
1755 #endif /* end of coloursel response handlers */
1756
1757 static void filefont_clicked(GtkButton *button, gpointer data)
1758 {
1759     struct dlgparam *dp = (struct dlgparam *)data;
1760     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
1761
1762     if (uc->ctrl->generic.type == CTRL_FILESELECT) {
1763 #ifdef USE_GTK_FILE_CHOOSER_DIALOG
1764         GtkWidget *filechoose = gtk_file_chooser_dialog_new
1765             (uc->ctrl->fileselect.title, GTK_WINDOW(dp->window),
1766              (uc->ctrl->fileselect.for_writing ?
1767               GTK_FILE_CHOOSER_ACTION_SAVE :
1768               GTK_FILE_CHOOSER_ACTION_OPEN),
1769              STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL,
1770              STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT,
1771              (const gchar *)NULL);
1772         gtk_window_set_modal(GTK_WINDOW(filechoose), TRUE);
1773         g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc);
1774         g_signal_connect(G_OBJECT(filechoose), "response",
1775                          G_CALLBACK(filechoose_response), (gpointer)dp);
1776         gtk_widget_show(filechoose);
1777 #else
1778         GtkWidget *filesel =
1779             gtk_file_selection_new(uc->ctrl->fileselect.title);
1780         gtk_window_set_modal(GTK_WINDOW(filesel), TRUE);
1781         g_object_set_data
1782             (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
1783              (gpointer)filesel);
1784         g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc);
1785         g_signal_connect
1786             (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
1787              G_CALLBACK(filesel_ok), (gpointer)dp);
1788         g_signal_connect_swapped
1789             (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
1790              G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
1791         g_signal_connect_swapped
1792             (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
1793              G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
1794         gtk_widget_show(filesel);
1795 #endif
1796     }
1797
1798     if (uc->ctrl->generic.type == CTRL_FONTSELECT) {
1799         const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry));
1800
1801 #if !GTK_CHECK_VERSION(2,0,0)
1802
1803         /*
1804          * Use the GTK 1 standard font selector.
1805          */
1806
1807         gchar *spacings[] = { "c", "m", NULL };
1808         GtkWidget *fontsel =
1809             gtk_font_selection_dialog_new("Select a font");
1810         gtk_window_set_modal(GTK_WINDOW(fontsel), TRUE);
1811         gtk_font_selection_dialog_set_filter
1812             (GTK_FONT_SELECTION_DIALOG(fontsel),
1813              GTK_FONT_FILTER_BASE, GTK_FONT_ALL,
1814              NULL, NULL, NULL, NULL, spacings, NULL);
1815         if (!gtk_font_selection_dialog_set_font_name
1816             (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) {
1817             /*
1818              * If the font name wasn't found as it was, try opening
1819              * it and extracting its FONT property. This should
1820              * have the effect of mapping short aliases into true
1821              * XLFDs.
1822              */
1823             GdkFont *font = gdk_font_load(fontname);
1824             if (font) {
1825                 XFontStruct *xfs = GDK_FONT_XFONT(font);
1826                 Display *disp = GDK_FONT_XDISPLAY(font);
1827                 Atom fontprop = XInternAtom(disp, "FONT", False);
1828                 unsigned long ret;
1829                 gdk_font_ref(font);
1830                 if (XGetFontProperty(xfs, fontprop, &ret)) {
1831                     char *name = XGetAtomName(disp, (Atom)ret);
1832                     if (name)
1833                         gtk_font_selection_dialog_set_font_name
1834                         (GTK_FONT_SELECTION_DIALOG(fontsel), name);
1835                 }
1836                 gdk_font_unref(font);
1837             }
1838         }
1839         g_object_set_data
1840             (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
1841              "user-data", (gpointer)fontsel);
1842         g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc);
1843         g_signal_connect
1844             (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
1845              "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp);
1846         g_signal_connect_swapped
1847             (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
1848              "clicked", G_CALLBACK(gtk_widget_destroy),
1849              (gpointer)fontsel);
1850         g_signal_connect_swapped
1851             (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button),
1852              "clicked", G_CALLBACK(gtk_widget_destroy),
1853              (gpointer)fontsel);
1854         gtk_widget_show(fontsel);
1855
1856 #else /* !GTK_CHECK_VERSION(2,0,0) */
1857
1858         /*
1859          * Use the unifontsel code provided in gtkfont.c.
1860          */
1861
1862         unifontsel *fontsel = unifontsel_new("Select a font");
1863
1864         gtk_window_set_modal(fontsel->window, TRUE);
1865         unifontsel_set_name(fontsel, fontname);
1866         
1867         g_object_set_data(G_OBJECT(fontsel->ok_button),
1868                           "user-data", (gpointer)fontsel);
1869         fontsel->user_data = uc;
1870         g_signal_connect(G_OBJECT(fontsel->ok_button), "clicked",
1871                          G_CALLBACK(fontsel_ok), (gpointer)dp);
1872         g_signal_connect_swapped(G_OBJECT(fontsel->ok_button), "clicked",
1873                                  G_CALLBACK(unifontsel_destroy),
1874                                  (gpointer)fontsel);
1875         g_signal_connect_swapped(G_OBJECT(fontsel->cancel_button),"clicked",
1876                                  G_CALLBACK(unifontsel_destroy),
1877                                  (gpointer)fontsel);
1878
1879         gtk_widget_show(GTK_WIDGET(fontsel->window));
1880
1881 #endif /* !GTK_CHECK_VERSION(2,0,0) */
1882
1883     }
1884 }
1885
1886 #if !GTK_CHECK_VERSION(3,0,0)
1887 static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc,
1888                             gpointer data)
1889 {
1890     struct dlgparam *dp = (struct dlgparam *)data;
1891     struct uctrl *uc = dlg_find_bywidget(dp, widget);
1892
1893     gtk_widget_set_size_request(uc->text, alloc->width, -1);
1894     gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label);
1895     g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig);
1896 }
1897 #endif
1898
1899 /* ----------------------------------------------------------------------
1900  * This function does the main layout work: it reads a controlset,
1901  * it creates the relevant GTK controls, and returns a GtkWidget
1902  * containing the result. (This widget might be a title of some
1903  * sort, it might be a Columns containing many controls, or it
1904  * might be a GtkFrame containing a Columns; whatever it is, it's
1905  * definitely a GtkWidget and should probably be added to a
1906  * GtkVbox.)
1907  * 
1908  * `win' is required for setting the default button. If it is
1909  * non-NULL, all buttons created will be default-capable (so they
1910  * have extra space round them for the default highlight).
1911  */
1912 GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
1913                         struct controlset *s, GtkWindow *win)
1914 {
1915     Columns *cols;
1916     GtkWidget *ret;
1917     int i;
1918
1919     if (!s->boxname && s->boxtitle) {
1920         /* This controlset is a panel title. */
1921         return gtk_label_new(s->boxtitle);
1922     }
1923
1924     /*
1925      * Otherwise, we expect to be laying out actual controls, so
1926      * we'll start by creating a Columns for the purpose.
1927      */
1928     cols = COLUMNS(columns_new(4));
1929     ret = GTK_WIDGET(cols);
1930     gtk_widget_show(ret);
1931
1932     /*
1933      * Create a containing frame if we have a box name.
1934      */
1935     if (*s->boxname) {
1936         ret = gtk_frame_new(s->boxtitle);   /* NULL is valid here */
1937         gtk_container_set_border_width(GTK_CONTAINER(cols), 4);
1938         gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols));
1939         gtk_widget_show(ret);
1940     }
1941
1942     /*
1943      * Now iterate through the controls themselves, create them,
1944      * and add them to the Columns.
1945      */
1946     for (i = 0; i < s->ncontrols; i++) {
1947         union control *ctrl = s->ctrls[i];
1948         struct uctrl *uc;
1949         int left = FALSE;
1950         GtkWidget *w = NULL;
1951
1952         switch (ctrl->generic.type) {
1953           case CTRL_COLUMNS:
1954             {
1955                 static const int simplecols[1] = { 100 };
1956                 columns_set_cols(cols, ctrl->columns.ncols,
1957                                  (ctrl->columns.percentages ?
1958                                   ctrl->columns.percentages : simplecols));
1959             }
1960             continue;                  /* no actual control created */
1961           case CTRL_TABDELAY:
1962             {
1963                 struct uctrl *uc = dlg_find_byctrl(dp, ctrl->tabdelay.ctrl);
1964                 if (uc)
1965                     columns_taborder_last(cols, uc->toplevel);
1966             }
1967             continue;                  /* no actual control created */
1968         }
1969
1970         uc = snew(struct uctrl);
1971         uc->ctrl = ctrl;
1972         uc->buttons = NULL;
1973         uc->entry = NULL;
1974 #if !GTK_CHECK_VERSION(2,4,0)
1975         uc->list = uc->menu = uc->optmenu = NULL;
1976 #else
1977         uc->combo = NULL;
1978 #endif
1979 #if GTK_CHECK_VERSION(2,0,0)
1980         uc->treeview = NULL;
1981         uc->listmodel = NULL;
1982 #endif
1983         uc->button = uc->text = NULL;
1984         uc->label = NULL;
1985         uc->nclicks = 0;
1986
1987         switch (ctrl->generic.type) {
1988           case CTRL_BUTTON:
1989             w = gtk_button_new_with_label(ctrl->generic.label);
1990             if (win) {
1991                 gtk_widget_set_can_default(w, TRUE);
1992                 if (ctrl->button.isdefault)
1993                     gtk_window_set_default(win, w);
1994                 if (ctrl->button.iscancel)
1995                     dp->cancelbutton = w;
1996             }
1997             g_signal_connect(G_OBJECT(w), "clicked",
1998                              G_CALLBACK(button_clicked), dp);
1999             g_signal_connect(G_OBJECT(w), "focus_in_event",
2000                              G_CALLBACK(widget_focus), dp);
2001             shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
2002                          ctrl->button.shortcut, SHORTCUT_UCTRL, uc);
2003             break;
2004           case CTRL_CHECKBOX:
2005             w = gtk_check_button_new_with_label(ctrl->generic.label);
2006             g_signal_connect(G_OBJECT(w), "toggled",
2007                              G_CALLBACK(button_toggled), dp);
2008             g_signal_connect(G_OBJECT(w), "focus_in_event",
2009                              G_CALLBACK(widget_focus), dp);
2010             shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
2011                          ctrl->checkbox.shortcut, SHORTCUT_UCTRL, uc);
2012             left = TRUE;
2013             break;
2014           case CTRL_RADIO:
2015             /*
2016              * Radio buttons get to go inside their own Columns, no
2017              * matter what.
2018              */
2019             {
2020                 gint i, *percentages;
2021                 GSList *group;
2022
2023                 w = columns_new(0);
2024                 if (ctrl->generic.label) {
2025                     GtkWidget *label = gtk_label_new(ctrl->generic.label);
2026                     columns_add(COLUMNS(w), label, 0, 1);
2027                     columns_force_left_align(COLUMNS(w), label);
2028                     gtk_widget_show(label);
2029                     shortcut_add(scs, label, ctrl->radio.shortcut,
2030                                  SHORTCUT_UCTRL, uc);
2031                     uc->label = label;
2032                 }
2033                 percentages = g_new(gint, ctrl->radio.ncolumns);
2034                 for (i = 0; i < ctrl->radio.ncolumns; i++) {
2035                     percentages[i] =
2036                         ((100 * (i+1) / ctrl->radio.ncolumns) -
2037                          100 * i / ctrl->radio.ncolumns);
2038                 }
2039                 columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns,
2040                                  percentages);
2041                 g_free(percentages);
2042                 group = NULL;
2043
2044                 uc->nbuttons = ctrl->radio.nbuttons;
2045                 uc->buttons = snewn(uc->nbuttons, GtkWidget *);
2046
2047                 for (i = 0; i < ctrl->radio.nbuttons; i++) {
2048                     GtkWidget *b;
2049                     gint colstart;
2050
2051                     b = (gtk_radio_button_new_with_label
2052                          (group, ctrl->radio.buttons[i]));
2053                     uc->buttons[i] = b;
2054                     group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b));
2055                     colstart = i % ctrl->radio.ncolumns;
2056                     columns_add(COLUMNS(w), b, colstart,
2057                                 (i == ctrl->radio.nbuttons-1 ?
2058                                  ctrl->radio.ncolumns - colstart : 1));
2059                     columns_force_left_align(COLUMNS(w), b);
2060                     gtk_widget_show(b);
2061                     g_signal_connect(G_OBJECT(b), "toggled",
2062                                      G_CALLBACK(button_toggled), dp);
2063                     g_signal_connect(G_OBJECT(b), "focus_in_event",
2064                                      G_CALLBACK(widget_focus), dp);
2065                     if (ctrl->radio.shortcuts) {
2066                         shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)),
2067                                      ctrl->radio.shortcuts[i],
2068                                      SHORTCUT_UCTRL, uc);
2069                     }
2070                 }
2071             }
2072             break;
2073           case CTRL_EDITBOX:
2074             {
2075                 GtkWidget *signalobject;
2076
2077                 if (ctrl->editbox.has_list) {
2078 #if !GTK_CHECK_VERSION(2,4,0)
2079                     /*
2080                      * GTK 1 combo box.
2081                      */
2082                     w = gtk_combo_new();
2083                     gtk_combo_set_value_in_list(GTK_COMBO(w), FALSE, TRUE);
2084                     uc->entry = GTK_COMBO(w)->entry;
2085                     uc->list = GTK_COMBO(w)->list;
2086                     signalobject = uc->entry;
2087 #else
2088                     /*
2089                      * GTK 2 combo box.
2090                      */
2091                     uc->listmodel = gtk_list_store_new(2, G_TYPE_INT,
2092                                                        G_TYPE_STRING);
2093                     w = gtk_combo_box_new_with_model_and_entry
2094                         (GTK_TREE_MODEL(uc->listmodel));
2095                     /* We cannot support password combo boxes. */
2096                     assert(!ctrl->editbox.password);
2097                     uc->combo = w;
2098                     signalobject = uc->combo;
2099 #endif
2100                 } else {
2101                     w = gtk_entry_new();
2102                     if (ctrl->editbox.password)
2103                         gtk_entry_set_visibility(GTK_ENTRY(w), FALSE);
2104                     uc->entry = w;
2105                     signalobject = w;
2106                 }
2107                 uc->entrysig =
2108                     g_signal_connect(G_OBJECT(signalobject), "changed",
2109                                      G_CALLBACK(editbox_changed), dp);
2110                 g_signal_connect(G_OBJECT(signalobject), "key_press_event",
2111                                  G_CALLBACK(editbox_key), dp);
2112                 g_signal_connect(G_OBJECT(signalobject), "focus_in_event",
2113                                  G_CALLBACK(widget_focus), dp);
2114                 g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
2115                                  G_CALLBACK(editbox_lostfocus), dp);
2116                 g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
2117                                  G_CALLBACK(editbox_lostfocus), dp);
2118
2119 #if !GTK_CHECK_VERSION(3,0,0)
2120                 /*
2121                  * Edit boxes, for some strange reason, have a minimum
2122                  * width of 150 in GTK 1.2. We don't want this - we'd
2123                  * rather the edit boxes acquired their natural width
2124                  * from the column layout of the rest of the box.
2125                  */
2126                 {
2127                     GtkRequisition req;
2128                     gtk_widget_size_request(w, &req);
2129                     gtk_widget_set_size_request(w, 10, req.height);
2130                 }
2131 #else
2132                 /*
2133                  * In GTK 3, this is still true, but there's a special
2134                  * method for GtkEntry in particular to fix it.
2135                  */
2136                 if (GTK_IS_ENTRY(w))
2137                     gtk_entry_set_width_chars(GTK_ENTRY(w), 1);
2138 #endif
2139
2140                 if (ctrl->generic.label) {
2141                     GtkWidget *label, *container;
2142
2143                     label = gtk_label_new(ctrl->generic.label);
2144
2145                     shortcut_add(scs, label, ctrl->editbox.shortcut,
2146                                  SHORTCUT_FOCUS, uc->entry);
2147
2148                     container = columns_new(4);
2149                     if (ctrl->editbox.percentwidth == 100) {
2150                         columns_add(COLUMNS(container), label, 0, 1);
2151                         columns_force_left_align(COLUMNS(container), label);
2152                         columns_add(COLUMNS(container), w, 0, 1);
2153                     } else {
2154                         gint percentages[2];
2155                         percentages[1] = ctrl->editbox.percentwidth;
2156                         percentages[0] = 100 - ctrl->editbox.percentwidth;
2157                         columns_set_cols(COLUMNS(container), 2, percentages);
2158                         columns_add(COLUMNS(container), label, 0, 1);
2159                         columns_force_left_align(COLUMNS(container), label);
2160                         columns_add(COLUMNS(container), w, 1, 1);
2161                         columns_force_same_height(COLUMNS(container),
2162                                                   label, w);
2163                     }
2164                     gtk_widget_show(label);
2165                     gtk_widget_show(w);
2166
2167                     w = container;
2168                     uc->label = label;
2169                 }
2170             }
2171             break;
2172           case CTRL_FILESELECT:
2173           case CTRL_FONTSELECT:
2174             {
2175                 GtkWidget *ww;
2176                 const char *browsebtn =
2177                     (ctrl->generic.type == CTRL_FILESELECT ?
2178                      "Browse..." : "Change...");
2179
2180                 gint percentages[] = { 75, 25 };
2181                 w = columns_new(4);
2182                 columns_set_cols(COLUMNS(w), 2, percentages);
2183
2184                 if (ctrl->generic.label) {
2185                     ww = gtk_label_new(ctrl->generic.label);
2186                     columns_add(COLUMNS(w), ww, 0, 2);
2187                     columns_force_left_align(COLUMNS(w), ww);
2188                     gtk_widget_show(ww);
2189                     shortcut_add(scs, ww,
2190                                  (ctrl->generic.type == CTRL_FILESELECT ?
2191                                   ctrl->fileselect.shortcut :
2192                                   ctrl->fontselect.shortcut),
2193                                  SHORTCUT_UCTRL, uc);
2194                     uc->label = ww;
2195                 }
2196
2197                 uc->entry = ww = gtk_entry_new();
2198 #if !GTK_CHECK_VERSION(3,0,0)
2199                 {
2200                     GtkRequisition req;
2201                     gtk_widget_size_request(ww, &req);
2202                     gtk_widget_set_size_request(ww, 10, req.height);
2203                 }
2204 #else
2205                 gtk_entry_set_width_chars(GTK_ENTRY(ww), 1);
2206 #endif
2207                 columns_add(COLUMNS(w), ww, 0, 1);
2208                 gtk_widget_show(ww);
2209
2210                 uc->button = ww = gtk_button_new_with_label(browsebtn);
2211                 columns_add(COLUMNS(w), ww, 1, 1);
2212                 gtk_widget_show(ww);
2213
2214                 columns_force_same_height(COLUMNS(w), uc->entry, uc->button);
2215
2216                 g_signal_connect(G_OBJECT(uc->entry), "key_press_event",
2217                                  G_CALLBACK(editbox_key), dp);
2218                 uc->entrysig =
2219                     g_signal_connect(G_OBJECT(uc->entry), "changed",
2220                                      G_CALLBACK(editbox_changed), dp);
2221                 g_signal_connect(G_OBJECT(uc->entry), "focus_in_event",
2222                                  G_CALLBACK(widget_focus), dp);
2223                 g_signal_connect(G_OBJECT(uc->button), "focus_in_event",
2224                                  G_CALLBACK(widget_focus), dp);
2225                 g_signal_connect(G_OBJECT(ww), "clicked",
2226                                  G_CALLBACK(filefont_clicked), dp);
2227             }
2228             break;
2229           case CTRL_LISTBOX:
2230
2231 #if GTK_CHECK_VERSION(2,0,0)
2232             /*
2233              * First construct the list data store, with the right
2234              * number of columns.
2235              */
2236 #  if !GTK_CHECK_VERSION(2,4,0)
2237             /* (For GTK 2.0 to 2.3, we do this for full listboxes only,
2238              * because combo boxes are still done the old GTK1 way.) */
2239             if (ctrl->listbox.height > 0)
2240 #  endif
2241             {
2242                 GType *types;
2243                 int i;
2244                 int cols;
2245
2246                 cols = ctrl->listbox.ncols;
2247                 cols = cols ? cols : 1;
2248                 types = snewn(1 + cols, GType);
2249
2250                 types[0] = G_TYPE_INT;
2251                 for (i = 0; i < cols; i++)
2252                     types[i+1] = G_TYPE_STRING;
2253
2254                 uc->listmodel = gtk_list_store_newv(1 + cols, types);
2255
2256                 sfree(types);
2257             }
2258 #endif
2259
2260             /*
2261              * See if it's a drop-down list (non-editable combo
2262              * box).
2263              */
2264             if (ctrl->listbox.height == 0) {
2265 #if !GTK_CHECK_VERSION(2,4,0)
2266                 /*
2267                  * GTK1 and early-GTK2 option-menu style of
2268                  * drop-down list.
2269                  */
2270                 uc->optmenu = w = gtk_option_menu_new();
2271                 uc->menu = gtk_menu_new();
2272                 gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu);
2273                 g_object_set_data(G_OBJECT(uc->menu), "user-data",
2274                                   (gpointer)uc->optmenu);
2275                 g_signal_connect(G_OBJECT(uc->optmenu), "focus_in_event",
2276                                  G_CALLBACK(widget_focus), dp);
2277 #else
2278                 /*
2279                  * Late-GTK2 style using a GtkComboBox.
2280                  */
2281                 GtkCellRenderer *cr;
2282
2283                 /*
2284                  * Create a non-editable GtkComboBox (that is, not
2285                  * its subclass GtkComboBoxEntry).
2286                  */
2287                 w = gtk_combo_box_new_with_model
2288                     (GTK_TREE_MODEL(uc->listmodel));
2289                 uc->combo = w;
2290
2291                 /*
2292                  * Tell it how to render a list item (i.e. which
2293                  * column to look at in the list model).
2294                  */
2295                 cr = gtk_cell_renderer_text_new();
2296                 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE);
2297                 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr,
2298                                                "text", 1, NULL);
2299
2300                 /*
2301                  * And tell it to notify us when the selection
2302                  * changes.
2303                  */
2304                 g_signal_connect(G_OBJECT(w), "changed",
2305                                  G_CALLBACK(droplist_selchange), dp);
2306 #endif
2307             } else {
2308 #if !GTK_CHECK_VERSION(2,0,0)
2309                 /*
2310                  * GTK1-style full list box.
2311                  */
2312                 uc->list = gtk_list_new();
2313                 if (ctrl->listbox.multisel == 2) {
2314                     gtk_list_set_selection_mode(GTK_LIST(uc->list),
2315                                                 GTK_SELECTION_EXTENDED);
2316                 } else if (ctrl->listbox.multisel == 1) {
2317                     gtk_list_set_selection_mode(GTK_LIST(uc->list),
2318                                                 GTK_SELECTION_MULTIPLE);
2319                 } else {
2320                     gtk_list_set_selection_mode(GTK_LIST(uc->list),
2321                                                 GTK_SELECTION_SINGLE);
2322                 }
2323                 w = gtk_scrolled_window_new(NULL, NULL);
2324                 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w),
2325                                                       uc->list);
2326                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
2327                                                GTK_POLICY_NEVER,
2328                                                GTK_POLICY_AUTOMATIC);
2329                 uc->adj = gtk_scrolled_window_get_vadjustment
2330                     (GTK_SCROLLED_WINDOW(w));
2331
2332                 gtk_widget_show(uc->list);
2333                 g_signal_connect(G_OBJECT(uc->list), "selection-changed",
2334                                  G_CALLBACK(list_selchange), dp);
2335                 g_signal_connect(G_OBJECT(uc->list), "focus_in_event",
2336                                  G_CALLBACK(widget_focus), dp);
2337
2338                 /*
2339                  * Adjust the height of the scrolled window to the
2340                  * minimum given by the height parameter.
2341                  * 
2342                  * This piece of guesswork is a horrid hack based
2343                  * on looking inside the GTK 1.2 sources
2344                  * (specifically gtkviewport.c, which appears to be
2345                  * the widget which provides the border around the
2346                  * scrolling area). Anyone lets me know how I can
2347                  * do this in a way which isn't at risk from GTK
2348                  * upgrades, I'd be grateful.
2349                  */
2350                 {
2351                     int edge;
2352                     edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
2353                     gtk_widget_set_size_request(w, 10,
2354                                          2*edge + (ctrl->listbox.height *
2355                                                    get_listitemheight(w)));
2356                 }
2357
2358                 if (ctrl->listbox.draglist) {
2359                     /*
2360                      * GTK doesn't appear to make it easy to
2361                      * implement a proper draggable list; so
2362                      * instead I'm just going to have to put an Up
2363                      * and a Down button to the right of the actual
2364                      * list box. Ah well.
2365                      */
2366                     GtkWidget *cols, *button;
2367                     static const gint percentages[2] = { 80, 20 };
2368
2369                     cols = columns_new(4);
2370                     columns_set_cols(COLUMNS(cols), 2, percentages);
2371                     columns_add(COLUMNS(cols), w, 0, 1);
2372                     gtk_widget_show(w);
2373                     button = gtk_button_new_with_label("Up");
2374                     columns_add(COLUMNS(cols), button, 1, 1);
2375                     gtk_widget_show(button);
2376                     g_signal_connect(G_OBJECT(button), "clicked",
2377                                      G_CALLBACK(draglist_up), dp);
2378                     g_signal_connect(G_OBJECT(button), "focus_in_event",
2379                                      G_CALLBACK(widget_focus), dp);
2380                     button = gtk_button_new_with_label("Down");
2381                     columns_add(COLUMNS(cols), button, 1, 1);
2382                     gtk_widget_show(button);
2383                     g_signal_connect(G_OBJECT(button), "clicked",
2384                                      G_CALLBACK(draglist_down), dp);
2385                     g_signal_connect(G_OBJECT(button), "focus_in_event",
2386                                      G_CALLBACK(widget_focus), dp);
2387
2388                     w = cols;
2389                 }
2390 #else
2391                 /*
2392                  * GTK2 treeview-based full list box.
2393                  */
2394                 GtkTreeSelection *sel;
2395
2396                 /*
2397                  * Create the list box itself, its columns, and
2398                  * its containing scrolled window.
2399                  */
2400                 w = gtk_tree_view_new_with_model
2401                     (GTK_TREE_MODEL(uc->listmodel));
2402                 g_object_set_data(G_OBJECT(uc->listmodel), "user-data",
2403                                   (gpointer)w);
2404                 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
2405                 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
2406                 gtk_tree_selection_set_mode
2407                     (sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE :
2408                      GTK_SELECTION_SINGLE);
2409                 uc->treeview = w;
2410                 g_signal_connect(G_OBJECT(w), "row-activated",
2411                                  G_CALLBACK(listbox_doubleclick), dp);
2412                 g_signal_connect(G_OBJECT(sel), "changed",
2413                                  G_CALLBACK(listbox_selchange), dp);
2414
2415                 if (ctrl->listbox.draglist) {
2416                     gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), TRUE);
2417                     g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted",
2418                                      G_CALLBACK(listbox_reorder), dp);
2419                 }
2420
2421                 {
2422                     int i;
2423                     int cols;
2424
2425                     cols = ctrl->listbox.ncols;
2426                     cols = cols ? cols : 1;
2427                     for (i = 0; i < cols; i++) {
2428                         GtkTreeViewColumn *column;
2429                         GtkCellRenderer *cellrend;
2430                         /*
2431                          * It appears that GTK 2 doesn't leave us any
2432                          * particularly sensible way to honour the
2433                          * "percentages" specification in the ctrl
2434                          * structure.
2435                          */
2436                         cellrend = gtk_cell_renderer_text_new();
2437                         if (!ctrl->listbox.hscroll) {
2438                             g_object_set(G_OBJECT(cellrend),
2439                                          "ellipsize", PANGO_ELLIPSIZE_END,
2440                                          "ellipsize-set", TRUE,
2441                                          (const char *)NULL);
2442                         }
2443                         column = gtk_tree_view_column_new_with_attributes
2444                             ("heading", cellrend, "text", i+1, (char *)NULL);
2445                         gtk_tree_view_column_set_sizing
2446                             (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2447                         gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
2448                     }
2449                 }
2450
2451                 {
2452                     GtkWidget *scroll;
2453
2454                     scroll = gtk_scrolled_window_new(NULL, NULL);
2455                     gtk_scrolled_window_set_shadow_type
2456                         (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
2457                     gtk_widget_show(w);
2458                     gtk_container_add(GTK_CONTAINER(scroll), w);
2459                     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
2460                                                    GTK_POLICY_AUTOMATIC,
2461                                                    GTK_POLICY_ALWAYS);
2462                     gtk_widget_set_size_request
2463                         (scroll, -1,
2464                          ctrl->listbox.height * get_listitemheight(w));
2465
2466                     w = scroll;
2467                 }
2468 #endif
2469             }
2470
2471             if (ctrl->generic.label) {
2472                 GtkWidget *label, *container;
2473
2474                 label = gtk_label_new(ctrl->generic.label);
2475 #if GTK_CHECK_VERSION(3,0,0)
2476                 gtk_label_set_width_chars(GTK_LABEL(label), 3);
2477 #endif
2478
2479                 shortcut_add(scs, label, ctrl->listbox.shortcut,
2480                              SHORTCUT_FOCUS, w);
2481
2482                 container = columns_new(4);
2483                 if (ctrl->listbox.percentwidth == 100) {
2484                     columns_add(COLUMNS(container), label, 0, 1);
2485                     columns_force_left_align(COLUMNS(container), label);
2486                     columns_add(COLUMNS(container), w, 0, 1);
2487                 } else {
2488                     gint percentages[2];
2489                     percentages[1] = ctrl->listbox.percentwidth;
2490                     percentages[0] = 100 - ctrl->listbox.percentwidth;
2491                     columns_set_cols(COLUMNS(container), 2, percentages);
2492                     columns_add(COLUMNS(container), label, 0, 1);
2493                     columns_force_left_align(COLUMNS(container), label);
2494                     columns_add(COLUMNS(container), w, 1, 1);
2495                     columns_force_same_height(COLUMNS(container),
2496                                               label, w);
2497                 }
2498                 gtk_widget_show(label);
2499                 gtk_widget_show(w);
2500
2501                 w = container;
2502                 uc->label = label;
2503             }
2504
2505             break;
2506           case CTRL_TEXT:
2507 #if !GTK_CHECK_VERSION(3,0,0)
2508             /*
2509              * Wrapping text widgets don't sit well with the GTK2
2510              * layout model, in which widgets state a minimum size
2511              * and the whole window then adjusts to the smallest
2512              * size it can sensibly take given its contents. A
2513              * wrapping text widget _has_ no clear minimum size;
2514              * instead it has a range of possibilities. It can be
2515              * one line deep but 2000 wide, or two lines deep and
2516              * 1000 pixels, or three by 867, or four by 500 and so
2517              * on. It can be as short as you like provided you
2518              * don't mind it being wide, or as narrow as you like
2519              * provided you don't mind it being tall.
2520              * 
2521              * Therefore, it fits very badly into the layout model.
2522              * Hence the only thing to do is pick a width and let
2523              * it choose its own number of lines. To do this I'm
2524              * going to cheat a little. All new wrapping text
2525              * widgets will be created with a minimal text content
2526              * "X"; then, after the rest of the dialog box is set
2527              * up and its size calculated, the text widgets will be
2528              * told their width and given their real text, which
2529              * will cause the size to be recomputed in the y
2530              * direction (because many of them will expand to more
2531              * than one line).
2532              */
2533             uc->text = w = gtk_label_new("X");
2534             uc->textsig =
2535                 g_signal_connect(G_OBJECT(w), "size-allocate",
2536                                  G_CALLBACK(label_sizealloc), dp);
2537 #else
2538             /*
2539              * In GTK3, this is all fixed, because the main aim of the
2540              * new 'height-for-width' geometry management is to make
2541              * wrapping labels behave sensibly. So now we can just do
2542              * the obvious thing.
2543              */
2544             uc->text = w = gtk_label_new(uc->ctrl->generic.label);
2545 #endif
2546             align_label_left(GTK_LABEL(w));
2547             gtk_label_set_line_wrap(GTK_LABEL(w), TRUE);
2548             break;
2549         }
2550
2551         assert(w != NULL);
2552
2553         columns_add(cols, w,
2554                     COLUMN_START(ctrl->generic.column),
2555                     COLUMN_SPAN(ctrl->generic.column));
2556         if (left)
2557             columns_force_left_align(cols, w);
2558         gtk_widget_show(w);
2559
2560         uc->toplevel = w;
2561         dlg_add_uctrl(dp, uc);
2562     }
2563
2564     return ret;
2565 }
2566
2567 struct selparam {
2568     struct dlgparam *dp;
2569     GtkNotebook *panels;
2570     GtkWidget *panel;
2571 #if !GTK_CHECK_VERSION(2,0,0)
2572     GtkWidget *treeitem;
2573 #else
2574     int depth;
2575     GtkTreePath *treepath;
2576 #endif
2577     struct Shortcuts shortcuts;
2578 };
2579
2580 #if GTK_CHECK_VERSION(2,0,0)
2581 static void treeselection_changed(GtkTreeSelection *treeselection,
2582                                   gpointer data)
2583 {
2584     struct selparam *sps = (struct selparam *)data, *sp;
2585     GtkTreeModel *treemodel;
2586     GtkTreeIter treeiter;
2587     gint spindex;
2588     gint page_num;
2589
2590     if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
2591         return;
2592
2593     gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1);
2594     sp = &sps[spindex];
2595
2596     page_num = gtk_notebook_page_num(sp->panels, sp->panel);
2597     gtk_notebook_set_current_page(sp->panels, page_num);
2598
2599     dlg_refresh(NULL, sp->dp);
2600
2601     sp->dp->shortcuts = &sp->shortcuts;
2602 }
2603 #else
2604 static void treeitem_sel(GtkItem *item, gpointer data)
2605 {
2606     struct selparam *sp = (struct selparam *)data;
2607     gint page_num;
2608
2609     page_num = gtk_notebook_page_num(sp->panels, sp->panel);
2610     gtk_notebook_set_page(sp->panels, page_num);
2611
2612     dlg_refresh(NULL, sp->dp);
2613
2614     sp->dp->shortcuts = &sp->shortcuts;
2615     sp->dp->currtreeitem = sp->treeitem;
2616 }
2617 #endif
2618
2619 static void window_destroy(GtkWidget *widget, gpointer data)
2620 {
2621     gtk_main_quit();
2622 }
2623
2624 #if !GTK_CHECK_VERSION(2,0,0)
2625 static int tree_grab_focus(struct dlgparam *dp)
2626 {
2627     int i, f;
2628
2629     /*
2630      * See if any of the treeitems has the focus.
2631      */
2632     f = -1;
2633     for (i = 0; i < dp->ntreeitems; i++)
2634         if (GTK_WIDGET_HAS_FOCUS(dp->treeitems[i])) {
2635             f = i;
2636             break;
2637         }
2638
2639     if (f >= 0)
2640         return FALSE;
2641     else {
2642         gtk_widget_grab_focus(dp->currtreeitem);
2643         return TRUE;
2644     }
2645 }
2646
2647 gint tree_focus(GtkContainer *container, GtkDirectionType direction,
2648                 gpointer data)
2649 {
2650     struct dlgparam *dp = (struct dlgparam *)data;
2651
2652     g_signal_stop_emission_by_name(G_OBJECT(container), "focus");
2653     /*
2654      * If there's a focused treeitem, we return FALSE to cause the
2655      * focus to move on to some totally other control. If not, we
2656      * focus the selected one.
2657      */
2658     return tree_grab_focus(dp);
2659 }
2660 #endif
2661
2662 int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
2663 {
2664     struct dlgparam *dp = (struct dlgparam *)data;
2665
2666     if (event->keyval == GDK_KEY_Escape && dp->cancelbutton) {
2667         g_signal_emit_by_name(G_OBJECT(dp->cancelbutton), "clicked");
2668         return TRUE;
2669     }
2670
2671     if ((event->state & GDK_MOD1_MASK) &&
2672         (unsigned char)event->string[0] > 0 &&
2673         (unsigned char)event->string[0] <= 127) {
2674         int schr = (unsigned char)event->string[0];
2675         struct Shortcut *sc = &dp->shortcuts->sc[schr];
2676
2677         switch (sc->action) {
2678           case SHORTCUT_TREE:
2679 #if GTK_CHECK_VERSION(2,0,0)
2680             gtk_widget_grab_focus(sc->widget);
2681 #else
2682             tree_grab_focus(dp);
2683 #endif
2684             break;
2685           case SHORTCUT_FOCUS:
2686             gtk_widget_grab_focus(sc->widget);
2687             break;
2688           case SHORTCUT_UCTRL:
2689             /*
2690              * We must do something sensible with a uctrl.
2691              * Precisely what this is depends on the type of
2692              * control.
2693              */
2694             switch (sc->uc->ctrl->generic.type) {
2695               case CTRL_CHECKBOX:
2696               case CTRL_BUTTON:
2697                 /* Check boxes and buttons get the focus _and_ get toggled. */
2698                 gtk_widget_grab_focus(sc->uc->toplevel);
2699                 g_signal_emit_by_name(G_OBJECT(sc->uc->toplevel), "clicked");
2700                 break;
2701               case CTRL_FILESELECT:
2702               case CTRL_FONTSELECT:
2703                 /* File/font selectors have their buttons pressed (ooer),
2704                  * and focus transferred to the edit box. */
2705                 g_signal_emit_by_name(G_OBJECT(sc->uc->button), "clicked");
2706                 gtk_widget_grab_focus(sc->uc->entry);
2707                 break;
2708               case CTRL_RADIO:
2709                 /*
2710                  * Radio buttons are fun, because they have
2711                  * multiple shortcuts. We must find whether the
2712                  * activated shortcut is the shortcut for the whole
2713                  * group, or for a particular button. In the former
2714                  * case, we find the currently selected button and
2715                  * focus it; in the latter, we focus-and-click the
2716                  * button whose shortcut was pressed.
2717                  */
2718                 if (schr == sc->uc->ctrl->radio.shortcut) {
2719                     int i;
2720                     for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
2721                         if (gtk_toggle_button_get_active
2722                             (GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) {
2723                             gtk_widget_grab_focus(sc->uc->buttons[i]);
2724                         }
2725                 } else if (sc->uc->ctrl->radio.shortcuts) {
2726                     int i;
2727                     for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
2728                         if (schr == sc->uc->ctrl->radio.shortcuts[i]) {
2729                             gtk_widget_grab_focus(sc->uc->buttons[i]);
2730                             g_signal_emit_by_name
2731                                 (G_OBJECT(sc->uc->buttons[i]), "clicked");
2732                         }
2733                 }
2734                 break;
2735               case CTRL_LISTBOX:
2736
2737 #if !GTK_CHECK_VERSION(2,4,0)
2738                 if (sc->uc->optmenu) {
2739                     GdkEventButton bev;
2740                     gint returnval;
2741
2742                     gtk_widget_grab_focus(sc->uc->optmenu);
2743                     /* Option menus don't work using the "clicked" signal.
2744                      * We need to manufacture a button press event :-/ */
2745                     bev.type = GDK_BUTTON_PRESS;
2746                     bev.button = 1;
2747                     g_signal_emit_by_name(G_OBJECT(sc->uc->optmenu),
2748                                           "button_press_event",
2749                                           &bev, &returnval);
2750                     break;
2751                 }
2752 #else
2753                 if (sc->uc->combo) {
2754                     gtk_widget_grab_focus(sc->uc->combo);
2755                     gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo));
2756                     break;
2757                 }
2758 #endif
2759 #if !GTK_CHECK_VERSION(2,0,0)
2760                 if (sc->uc->list) {
2761                     /*
2762                      * For GTK-1 style list boxes, we tell it to
2763                      * focus one of its children, which appears to
2764                      * do the Right Thing.
2765                      */
2766                     gtk_container_focus(GTK_CONTAINER(sc->uc->list),
2767                                         GTK_DIR_TAB_FORWARD);
2768                     break;
2769                 }
2770 #else
2771                 if (sc->uc->treeview) {
2772                     gtk_widget_grab_focus(sc->uc->treeview);
2773                     break;
2774                 }
2775 #endif
2776                 assert(!"We shouldn't get here");
2777                 break;
2778             }
2779             break;
2780         }
2781     }
2782
2783     return FALSE;
2784 }
2785
2786 #if !GTK_CHECK_VERSION(2,0,0)
2787 int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
2788 {
2789     struct dlgparam *dp = (struct dlgparam *)data;
2790
2791     if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
2792         event->keyval == GDK_Down || event->keyval == GDK_KP_Down) {
2793         int dir, i, j = -1;
2794         for (i = 0; i < dp->ntreeitems; i++)
2795             if (widget == dp->treeitems[i])
2796                 break;
2797         if (i < dp->ntreeitems) {
2798             if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
2799                 dir = -1;
2800             else
2801                 dir = +1;
2802
2803             while (1) {
2804                 i += dir;
2805                 if (i < 0 || i >= dp->ntreeitems)
2806                     break;             /* nothing in that dir to select */
2807                 /*
2808                  * Determine if this tree item is visible.
2809                  */
2810                 {
2811                     GtkWidget *w = dp->treeitems[i];
2812                     int vis = TRUE;
2813                     while (w && (GTK_IS_TREE_ITEM(w) || GTK_IS_TREE(w))) {
2814                         if (!GTK_WIDGET_VISIBLE(w)) {
2815                             vis = FALSE;
2816                             break;
2817                         }
2818                         w = w->parent;
2819                     }
2820                     if (vis) {
2821                         j = i;         /* got one */
2822                         break;
2823                     }
2824                 }
2825             }
2826         }
2827         g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
2828         if (j >= 0) {
2829             g_signal_emit_by_name(G_OBJECT(dp->treeitems[j]), "toggle");
2830             gtk_widget_grab_focus(dp->treeitems[j]);
2831         }
2832         return TRUE;
2833     }
2834
2835     /*
2836      * It's nice for Left and Right to expand and collapse tree
2837      * branches.
2838      */
2839     if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left) {
2840         g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
2841         gtk_tree_item_collapse(GTK_TREE_ITEM(widget));
2842         return TRUE;
2843     }
2844     if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right) {
2845         g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
2846         gtk_tree_item_expand(GTK_TREE_ITEM(widget));
2847         return TRUE;
2848     }
2849
2850     return FALSE;
2851 }
2852 #endif
2853
2854 static void shortcut_highlight(GtkWidget *labelw, int chr)
2855 {
2856     GtkLabel *label = GTK_LABEL(labelw);
2857     const gchar *currstr;
2858     gchar *pattern;
2859     int i;
2860
2861 #if !GTK_CHECK_VERSION(2,0,0)
2862     {
2863         gchar *currstr_nonconst;
2864         gtk_label_get(label, &currstr_nonconst);
2865         currstr = currstr_nonconst;
2866     }
2867 #else
2868     currstr = gtk_label_get_text(label);
2869 #endif
2870
2871     for (i = 0; currstr[i]; i++)
2872         if (tolower((unsigned char)currstr[i]) == chr) {
2873             pattern = dupprintf("%*s_", i, "");
2874             gtk_label_set_pattern(label, pattern);
2875             sfree(pattern);
2876             break;
2877         }
2878 }
2879
2880 void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
2881                   int chr, int action, void *ptr)
2882 {
2883     if (chr == NO_SHORTCUT)
2884         return;
2885
2886     chr = tolower((unsigned char)chr);
2887
2888     assert(scs->sc[chr].action == SHORTCUT_EMPTY);
2889
2890     scs->sc[chr].action = action;
2891
2892     if (action == SHORTCUT_FOCUS) {
2893         scs->sc[chr].uc = NULL;
2894         scs->sc[chr].widget = (GtkWidget *)ptr;
2895     } else {
2896         scs->sc[chr].widget = NULL;
2897         scs->sc[chr].uc = (struct uctrl *)ptr;
2898     }
2899
2900     shortcut_highlight(labelw, chr);
2901 }
2902
2903 int get_listitemheight(GtkWidget *w)
2904 {
2905 #if !GTK_CHECK_VERSION(2,0,0)
2906     GtkWidget *listitem = gtk_list_item_new_with_label("foo");
2907     GtkRequisition req;
2908     gtk_widget_size_request(listitem, &req);
2909     g_object_ref_sink(G_OBJECT(listitem));
2910     return req.height;
2911 #else
2912     int height;
2913     GtkCellRenderer *cr = gtk_cell_renderer_text_new();
2914 #if GTK_CHECK_VERSION(3,0,0)
2915     {
2916         GtkRequisition req;
2917         /*
2918          * Since none of my list items wraps in this GUI, no
2919          * interesting width-for-height behaviour should be happening,
2920          * so I don't think it should matter here whether I ask for
2921          * the minimum or natural height.
2922          */
2923         gtk_cell_renderer_get_preferred_size(cr, w, &req, NULL);
2924         height = req.height;
2925     }
2926 #else
2927     gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height);
2928 #endif
2929     g_object_ref(G_OBJECT(cr));
2930     g_object_ref_sink(G_OBJECT(cr));
2931     g_object_unref(G_OBJECT(cr));
2932     return height;
2933 #endif
2934 }
2935
2936 #if GTK_CHECK_VERSION(2,0,0)
2937 void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree)
2938 {
2939     /*
2940      * Collapse the deeper branches of the treeview into the state we
2941      * like them to start off in. See comment below in do_config_box.
2942      */
2943     int i;
2944     for (i = 0; i < dp->nselparams; i++)
2945         if (dp->selparams[i].depth >= 2)
2946             gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree),
2947                                        dp->selparams[i].treepath);
2948 }
2949 #endif
2950
2951 #if GTK_CHECK_VERSION(3,0,0)
2952 void treeview_map_event(GtkWidget *tree, gpointer data)
2953 {
2954     struct dlgparam *dp = (struct dlgparam *)data;
2955     GtkAllocation alloc;
2956     gtk_widget_get_allocation(tree, &alloc);
2957     gtk_widget_set_size_request(tree, alloc.width, -1);
2958     initial_treeview_collapse(dp, tree);
2959 }
2960 #endif
2961
2962 int do_config_box(const char *title, Conf *conf, int midsession,
2963                   int protcfginfo)
2964 {
2965     GtkWidget *window, *hbox, *vbox, *cols, *label,
2966         *tree, *treescroll, *panels, *panelvbox;
2967     int index, level, protocol;
2968     struct controlbox *ctrlbox;
2969     char *path;
2970 #if GTK_CHECK_VERSION(2,0,0)
2971     GtkTreeStore *treestore;
2972     GtkCellRenderer *treerenderer;
2973     GtkTreeViewColumn *treecolumn;
2974     GtkTreeSelection *treeselection;
2975     GtkTreeIter treeiterlevels[8];
2976 #else
2977     GtkTreeItem *treeitemlevels[8];
2978     GtkTree *treelevels[8];
2979 #endif
2980     struct dlgparam dp;
2981     struct Shortcuts scs;
2982
2983     struct selparam *selparams = NULL;
2984     int nselparams = 0, selparamsize = 0;
2985
2986     dlg_init(&dp);
2987
2988     for (index = 0; index < lenof(scs.sc); index++) {
2989         scs.sc[index].action = SHORTCUT_EMPTY;
2990     }
2991
2992     window = our_dialog_new();
2993
2994     ctrlbox = ctrl_new_box();
2995     protocol = conf_get_int(conf, CONF_protocol);
2996     setup_config_box(ctrlbox, midsession, protocol, protcfginfo);
2997     unix_setup_config_box(ctrlbox, midsession, protocol);
2998     gtk_setup_config_box(ctrlbox, midsession, window);
2999
3000     gtk_window_set_title(GTK_WINDOW(window), title);
3001     hbox = gtk_hbox_new(FALSE, 4);
3002     our_dialog_add_to_content_area(GTK_WINDOW(window), hbox, TRUE, TRUE, 0);
3003     gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
3004     gtk_widget_show(hbox);
3005     vbox = gtk_vbox_new(FALSE, 4);
3006     gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
3007     gtk_widget_show(vbox);
3008     cols = columns_new(4);
3009     gtk_box_pack_start(GTK_BOX(vbox), cols, FALSE, FALSE, 0);
3010     gtk_widget_show(cols);
3011     label = gtk_label_new("Category:");
3012     columns_add(COLUMNS(cols), label, 0, 1);
3013     columns_force_left_align(COLUMNS(cols), label);
3014     gtk_widget_show(label);
3015     treescroll = gtk_scrolled_window_new(NULL, NULL);
3016 #if GTK_CHECK_VERSION(2,0,0)
3017     treestore = gtk_tree_store_new
3018         (TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT);
3019     tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore));
3020     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
3021     treerenderer = gtk_cell_renderer_text_new();
3022     treecolumn = gtk_tree_view_column_new_with_attributes
3023         ("Label", treerenderer, "text", 0, NULL);
3024     gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn);
3025     treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
3026     gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE);
3027     gtk_container_add(GTK_CONTAINER(treescroll), tree);
3028 #else
3029     tree = gtk_tree_new();
3030     gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
3031     gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
3032     g_signal_connect(G_OBJECT(tree), "focus",
3033                      G_CALLBACK(tree_focus), &dp);
3034 #endif
3035     g_signal_connect(G_OBJECT(tree), "focus_in_event",
3036                      G_CALLBACK(widget_focus), &dp);
3037     shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree);
3038     gtk_widget_show(treescroll);
3039     gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0);
3040     panels = gtk_notebook_new();
3041     gtk_notebook_set_show_tabs(GTK_NOTEBOOK(panels), FALSE);
3042     gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), FALSE);
3043     gtk_box_pack_start(GTK_BOX(hbox), panels, TRUE, TRUE, 0);
3044     gtk_widget_show(panels);
3045
3046     panelvbox = NULL;
3047     path = NULL;
3048     level = 0;
3049     for (index = 0; index < ctrlbox->nctrlsets; index++) {
3050         struct controlset *s = ctrlbox->ctrlsets[index];
3051         GtkWidget *w;
3052
3053         if (!*s->pathname) {
3054             w = layout_ctrls(&dp, &scs, s, GTK_WINDOW(window));
3055
3056             our_dialog_set_action_area(GTK_WINDOW(window), w);
3057         } else {
3058             int j = path ? ctrl_path_compare(s->pathname, path) : 0;
3059             if (j != INT_MAX) {        /* add to treeview, start new panel */
3060                 char *c;
3061 #if GTK_CHECK_VERSION(2,0,0)
3062                 GtkTreeIter treeiter;
3063 #else
3064                 GtkWidget *treeitem;
3065 #endif
3066                 int first;
3067
3068                 /*
3069                  * We expect never to find an implicit path
3070                  * component. For example, we expect never to see
3071                  * A/B/C followed by A/D/E, because that would
3072                  * _implicitly_ create A/D. All our path prefixes
3073                  * are expected to contain actual controls and be
3074                  * selectable in the treeview; so we would expect
3075                  * to see A/D _explicitly_ before encountering
3076                  * A/D/E.
3077                  */
3078                 assert(j == ctrl_path_elements(s->pathname) - 1);
3079
3080                 c = strrchr(s->pathname, '/');
3081                 if (!c)
3082                     c = s->pathname;
3083                 else
3084                     c++;
3085
3086                 path = s->pathname;
3087
3088                 first = (panelvbox == NULL);
3089
3090                 panelvbox = gtk_vbox_new(FALSE, 4);
3091                 gtk_widget_show(panelvbox);
3092                 gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox,
3093                                          NULL);
3094                 if (first) {
3095                     gint page_num;
3096
3097                     page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels),
3098                                                      panelvbox);
3099                     gtk_notebook_set_current_page(GTK_NOTEBOOK(panels),
3100                                                   page_num);
3101                 }
3102
3103                 if (nselparams >= selparamsize) {
3104                     selparamsize += 16;
3105                     selparams = sresize(selparams, selparamsize,
3106                                         struct selparam);
3107                 }
3108                 selparams[nselparams].dp = &dp;
3109                 selparams[nselparams].panels = GTK_NOTEBOOK(panels);
3110                 selparams[nselparams].panel = panelvbox;
3111                 selparams[nselparams].shortcuts = scs;   /* structure copy */
3112
3113                 assert(j-1 < level);
3114
3115 #if GTK_CHECK_VERSION(2,0,0)
3116                 if (j > 0)
3117                     /* treeiterlevels[j-1] will always be valid because we
3118                      * don't allow implicit path components; see above.
3119                      */
3120                     gtk_tree_store_append(treestore, &treeiter,
3121                                           &treeiterlevels[j-1]);
3122                 else
3123                     gtk_tree_store_append(treestore, &treeiter, NULL);
3124                 gtk_tree_store_set(treestore, &treeiter,
3125                                    TREESTORE_PATH, c,
3126                                    TREESTORE_PARAMS, nselparams,
3127                                    -1);
3128                 treeiterlevels[j] = treeiter;
3129
3130                 selparams[nselparams].depth = j;
3131                 if (j > 0) {
3132                     selparams[nselparams].treepath =
3133                         gtk_tree_model_get_path(GTK_TREE_MODEL(treestore),
3134                                                 &treeiterlevels[j-1]);
3135                     /*
3136                      * We are going to collapse all tree branches
3137                      * at depth greater than 2, but not _yet_; see
3138                      * the comment at the call to
3139                      * gtk_tree_view_collapse_row below.
3140                      */
3141                     gtk_tree_view_expand_row(GTK_TREE_VIEW(tree),
3142                                              selparams[nselparams].treepath,
3143                                              FALSE);
3144                 } else {
3145                     selparams[nselparams].treepath = NULL;
3146                 }
3147 #else
3148                 treeitem = gtk_tree_item_new_with_label(c);
3149                 if (j > 0) {
3150                     if (!treelevels[j-1]) {
3151                         treelevels[j-1] = GTK_TREE(gtk_tree_new());
3152                         gtk_tree_item_set_subtree
3153                             (treeitemlevels[j-1],
3154                              GTK_WIDGET(treelevels[j-1]));
3155                         if (j < 2)
3156                             gtk_tree_item_expand(treeitemlevels[j-1]);
3157                         else
3158                             gtk_tree_item_collapse(treeitemlevels[j-1]);
3159                     }
3160                     gtk_tree_append(treelevels[j-1], treeitem);
3161                 } else {
3162                     gtk_tree_append(GTK_TREE(tree), treeitem);
3163                 }
3164                 treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
3165                 treelevels[j] = NULL;
3166
3167                 g_signal_connect(G_OBJECT(treeitem), "key_press_event",
3168                                  G_CALLBACK(tree_key_press), &dp);
3169                 g_signal_connect(G_OBJECT(treeitem), "focus_in_event",
3170                                  G_CALLBACK(widget_focus), &dp);
3171
3172                 gtk_widget_show(treeitem);
3173
3174                 if (first)
3175                     gtk_tree_select_child(GTK_TREE(tree), treeitem);
3176                 selparams[nselparams].treeitem = treeitem;
3177 #endif
3178
3179                 level = j+1;
3180                 nselparams++;
3181             }
3182
3183             w = layout_ctrls(&dp, &selparams[nselparams-1].shortcuts, s, NULL);
3184             gtk_box_pack_start(GTK_BOX(panelvbox), w, FALSE, FALSE, 0);
3185             gtk_widget_show(w);
3186         }
3187     }
3188
3189 #if GTK_CHECK_VERSION(2,0,0)
3190     /*
3191      * We want our tree view to come up with all branches at depth 2
3192      * or more collapsed. However, if we start off with those branches
3193      * collapsed, then the tree view's size request will be calculated
3194      * based on the width of the collapsed tree, and then when the
3195      * collapsed branches are expanded later, the tree view will
3196      * jarringly change size.
3197      *
3198      * So instead we start with everything expanded; then, once the
3199      * tree view has computed its resulting width requirement, we
3200      * collapse the relevant rows, but force the width to be the value
3201      * we just retrieved. This arranges that the tree view is wide
3202      * enough to have all branches expanded without further resizing.
3203      */
3204
3205     dp.nselparams = nselparams;
3206     dp.selparams = selparams;
3207
3208 #if !GTK_CHECK_VERSION(3,0,0)
3209     {
3210         /*
3211          * In GTK2, we can just do the job right now.
3212          */
3213         GtkRequisition req;
3214         gtk_widget_size_request(tree, &req);
3215         initial_treeview_collapse(&dp, tree);
3216         gtk_widget_set_size_request(tree, req.width, -1);
3217     }
3218 #else
3219     /*
3220      * But in GTK3, we have to wait until the widget is about to be
3221      * mapped, because the size computation won't have been done yet.
3222      */
3223     g_signal_connect(G_OBJECT(tree), "map",
3224                      G_CALLBACK(treeview_map_event), &dp);
3225 #endif /* GTK 2 vs 3 */
3226 #endif /* GTK 2+ vs 1 */
3227
3228 #if GTK_CHECK_VERSION(2,0,0)
3229     g_signal_connect(G_OBJECT(treeselection), "changed",
3230                      G_CALLBACK(treeselection_changed), selparams);
3231 #else
3232     dp.ntreeitems = nselparams;
3233     dp.treeitems = snewn(dp.ntreeitems, GtkWidget *);
3234     for (index = 0; index < nselparams; index++) {
3235         g_signal_connect(G_OBJECT(selparams[index].treeitem), "select",
3236                          G_CALLBACK(treeitem_sel),
3237                          &selparams[index]);
3238         dp.treeitems[index] = selparams[index].treeitem;
3239     }
3240 #endif
3241
3242     dp.data = conf;
3243     dlg_refresh(NULL, &dp);
3244
3245     dp.shortcuts = &selparams[0].shortcuts;
3246 #if !GTK_CHECK_VERSION(2,0,0)
3247     dp.currtreeitem = dp.treeitems[0];
3248 #endif
3249     dp.lastfocus = NULL;
3250     dp.retval = 0;
3251     dp.window = window;
3252
3253     {
3254         /* in gtkwin.c */
3255         extern void set_window_icon(GtkWidget *window,
3256                                     const char *const *const *icon,
3257                                     int n_icon);
3258         extern const char *const *const cfg_icon[];
3259         extern const int n_cfg_icon;
3260         set_window_icon(window, cfg_icon, n_cfg_icon);
3261     }
3262
3263 #if !GTK_CHECK_VERSION(2,0,0)
3264     gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
3265                                           tree);
3266 #endif
3267     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
3268                                    GTK_POLICY_NEVER,
3269                                    GTK_POLICY_AUTOMATIC);
3270     gtk_widget_show(tree);
3271
3272     gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
3273     gtk_widget_show(window);
3274
3275     /*
3276      * Set focus into the first available control.
3277      */
3278     for (index = 0; index < ctrlbox->nctrlsets; index++) {
3279         struct controlset *s = ctrlbox->ctrlsets[index];
3280         int done = 0;
3281         int j;
3282
3283         if (*s->pathname) {
3284             for (j = 0; j < s->ncontrols; j++)
3285                 if (s->ctrls[j]->generic.type != CTRL_TABDELAY &&
3286                     s->ctrls[j]->generic.type != CTRL_COLUMNS &&
3287                     s->ctrls[j]->generic.type != CTRL_TEXT) {
3288                     dlg_set_focus(s->ctrls[j], &dp);
3289                     dp.lastfocus = s->ctrls[j];
3290                     done = 1;
3291                     break;
3292                 }
3293         }
3294         if (done)
3295             break;
3296     }
3297
3298     g_signal_connect(G_OBJECT(window), "destroy",
3299                      G_CALLBACK(window_destroy), NULL);
3300     g_signal_connect(G_OBJECT(window), "key_press_event",
3301                      G_CALLBACK(win_key_press), &dp);
3302
3303     gtk_main();
3304     post_main();
3305
3306     dlg_cleanup(&dp);
3307     sfree(selparams);
3308
3309     return dp.retval;
3310 }
3311
3312 static void messagebox_handler(union control *ctrl, void *dlg,
3313                                void *data, int event)
3314 {
3315     if (event == EVENT_ACTION)
3316         dlg_end(dlg, ctrl->generic.context.i);
3317 }
3318 int messagebox(GtkWidget *parentwin, const char *title, const char *msg,
3319                int minwid, ...)
3320 {
3321     GtkWidget *window, *w0, *w1;
3322     struct controlbox *ctrlbox;
3323     struct controlset *s0, *s1;
3324     union control *c;
3325     struct dlgparam dp;
3326     struct Shortcuts scs;
3327     int index, ncols;
3328     va_list ap;
3329
3330     dlg_init(&dp);
3331
3332     for (index = 0; index < lenof(scs.sc); index++) {
3333         scs.sc[index].action = SHORTCUT_EMPTY;
3334     }
3335
3336     ctrlbox = ctrl_new_box();
3337
3338     ncols = 0;
3339     va_start(ap, minwid);
3340     while (va_arg(ap, char *) != NULL) {
3341         ncols++;
3342         (void) va_arg(ap, int);        /* shortcut */
3343         (void) va_arg(ap, int);        /* normal/default/cancel */
3344         (void) va_arg(ap, int);        /* end value */
3345     }
3346     va_end(ap);
3347
3348     s0 = ctrl_getset(ctrlbox, "", "", "");
3349     c = ctrl_columns(s0, 2, 50, 50);
3350     c->columns.ncols = s0->ncolumns = ncols;
3351     c->columns.percentages = sresize(c->columns.percentages, ncols, int);
3352     for (index = 0; index < ncols; index++)
3353         c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols;
3354     va_start(ap, minwid);
3355     index = 0;
3356     while (1) {
3357         char *title = va_arg(ap, char *);
3358         int shortcut, type, value;
3359         if (title == NULL)
3360             break;
3361         shortcut = va_arg(ap, int);
3362         type = va_arg(ap, int);
3363         value = va_arg(ap, int);
3364         c = ctrl_pushbutton(s0, title, shortcut, HELPCTX(no_help),
3365                             messagebox_handler, I(value));
3366         c->generic.column = index++;
3367         if (type > 0)
3368             c->button.isdefault = TRUE;
3369         else if (type < 0)
3370             c->button.iscancel = TRUE;
3371     }
3372     va_end(ap);
3373
3374     s1 = ctrl_getset(ctrlbox, "x", "", "");
3375     ctrl_text(s1, msg, HELPCTX(no_help));
3376
3377     window = our_dialog_new();
3378     gtk_window_set_title(GTK_WINDOW(window), title);
3379     w0 = layout_ctrls(&dp, &scs, s0, GTK_WINDOW(window));
3380     our_dialog_set_action_area(GTK_WINDOW(window), w0);
3381     gtk_widget_show(w0);
3382     w1 = layout_ctrls(&dp, &scs, s1, GTK_WINDOW(window));
3383     gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
3384     gtk_widget_set_size_request(w1, minwid+20, -1);
3385     our_dialog_add_to_content_area(GTK_WINDOW(window), w1, TRUE, TRUE, 0);
3386     gtk_widget_show(w1);
3387
3388     dp.shortcuts = &scs;
3389     dp.lastfocus = NULL;
3390     dp.retval = 0;
3391     dp.window = window;
3392
3393     gtk_window_set_modal(GTK_WINDOW(window), TRUE);
3394     if (parentwin) {
3395         set_transient_window_pos(parentwin, window);
3396         gtk_window_set_transient_for(GTK_WINDOW(window),
3397                                      GTK_WINDOW(parentwin));
3398     } else
3399         gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
3400     gtk_widget_show(window);
3401
3402     g_signal_connect(G_OBJECT(window), "destroy",
3403                      G_CALLBACK(window_destroy), NULL);
3404     g_signal_connect(G_OBJECT(window), "key_press_event",
3405                      G_CALLBACK(win_key_press), &dp);
3406
3407     gtk_main();
3408     post_main();
3409
3410     dlg_cleanup(&dp);
3411     ctrl_free_box(ctrlbox);
3412
3413     return dp.retval;
3414 }
3415
3416 int string_width(const char *text)
3417 {
3418     int ret;
3419     get_label_text_dimensions(text, &ret, NULL);
3420     return ret;
3421 }
3422
3423 int reallyclose(void *frontend)
3424 {
3425     char *title = dupcat(appname, " Exit Confirmation", NULL);
3426     int ret = messagebox(GTK_WIDGET(get_window(frontend)),
3427                          title, "Are you sure you want to close this session?",
3428                          string_width("Most of the width of the above text"),
3429                          "Yes", 'y', +1, 1,
3430                          "No", 'n', -1, 0,
3431                          NULL);
3432     sfree(title);
3433     return ret;
3434 }
3435
3436 int verify_ssh_host_key(void *frontend, char *host, int port,
3437                         const char *keytype, char *keystr, char *fingerprint,
3438                         void (*callback)(void *ctx, int result), void *ctx)
3439 {
3440     static const char absenttxt[] =
3441         "The server's host key is not cached. You have no guarantee "
3442         "that the server is the computer you think it is.\n"
3443         "The server's %s key fingerprint is:\n"
3444         "%s\n"
3445         "If you trust this host, press \"Accept\" to add the key to "
3446         "PuTTY's cache and carry on connecting.\n"
3447         "If you want to carry on connecting just once, without "
3448         "adding the key to the cache, press \"Connect Once\".\n"
3449         "If you do not trust this host, press \"Cancel\" to abandon the "
3450         "connection.";
3451     static const char wrongtxt[] =
3452         "WARNING - POTENTIAL SECURITY BREACH!\n"
3453         "The server's host key does not match the one PuTTY has "
3454         "cached. This means that either the server administrator "
3455         "has changed the host key, or you have actually connected "
3456         "to another computer pretending to be the server.\n"
3457         "The new %s key fingerprint is:\n"
3458         "%s\n"
3459         "If you were expecting this change and trust the new key, "
3460         "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
3461         "If you want to carry on connecting but without updating "
3462         "the cache, press \"Connect Once\".\n"
3463         "If you want to abandon the connection completely, press "
3464         "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "
3465         "safe choice.";
3466     char *text;
3467     int ret;
3468
3469     /*
3470      * Verify the key.
3471      */
3472     ret = verify_host_key(host, port, keytype, keystr);
3473
3474     if (ret == 0)                      /* success - key matched OK */
3475         return 1;
3476
3477     text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);
3478
3479     ret = messagebox(GTK_WIDGET(get_window(frontend)),
3480                      "PuTTY Security Alert", text,
3481                      string_width(fingerprint),
3482                      "Accept", 'a', 0, 2,
3483                      "Connect Once", 'o', 0, 1,
3484                      "Cancel", 'c', -1, 0,
3485                      NULL);
3486
3487     sfree(text);
3488
3489     if (ret == 2) {
3490         store_host_key(host, port, keytype, keystr);
3491         return 1;                      /* continue with connection */
3492     } else if (ret == 1)
3493         return 1;                      /* continue with connection */
3494     return 0;                          /* do not continue with connection */
3495 }
3496
3497 /*
3498  * Ask whether the selected algorithm is acceptable (since it was
3499  * below the configured 'warn' threshold).
3500  */
3501 int askalg(void *frontend, const char *algtype, const char *algname,
3502            void (*callback)(void *ctx, int result), void *ctx)
3503 {
3504     static const char msg[] =
3505         "The first %s supported by the server is "
3506         "%s, which is below the configured warning threshold.\n"
3507         "Continue with connection?";
3508     char *text;
3509     int ret;
3510
3511     text = dupprintf(msg, algtype, algname);
3512     ret = messagebox(GTK_WIDGET(get_window(frontend)),
3513                      "PuTTY Security Alert", text,
3514                      string_width("Continue with connection?"),
3515                      "Yes", 'y', 0, 1,
3516                      "No", 'n', 0, 0,
3517                      NULL);
3518     sfree(text);
3519
3520     if (ret) {
3521         return 1;
3522     } else {
3523         return 0;
3524     }
3525 }
3526
3527 void old_keyfile_warning(void)
3528 {
3529     /*
3530      * This should never happen on Unix. We hope.
3531      */
3532 }
3533
3534 void fatal_message_box(void *window, const char *msg)
3535 {
3536     messagebox(window, "PuTTY Fatal Error", msg,
3537                string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
3538                "OK", 'o', 1, 1, NULL);
3539 }
3540
3541 void nonfatal_message_box(void *window, const char *msg)
3542 {
3543     messagebox(window, "PuTTY Error", msg,
3544                string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
3545                "OK", 'o', 1, 1, NULL);
3546 }
3547
3548 void fatalbox(const char *p, ...)
3549 {
3550     va_list ap;
3551     char *msg;
3552     va_start(ap, p);
3553     msg = dupvprintf(p, ap);
3554     va_end(ap);
3555     fatal_message_box(NULL, msg);
3556     sfree(msg);
3557     cleanup_exit(1);
3558 }
3559
3560 void nonfatal(const char *p, ...)
3561 {
3562     va_list ap;
3563     char *msg;
3564     va_start(ap, p);
3565     msg = dupvprintf(p, ap);
3566     va_end(ap);
3567     nonfatal_message_box(NULL, msg);
3568     sfree(msg);
3569 }
3570
3571 static GtkWidget *aboutbox = NULL;
3572
3573 static void about_close_clicked(GtkButton *button, gpointer data)
3574 {
3575     gtk_widget_destroy(aboutbox);
3576     aboutbox = NULL;
3577 }
3578
3579 static void licence_clicked(GtkButton *button, gpointer data)
3580 {
3581     char *title;
3582
3583     const char *licence =
3584         "Copyright 1997-2015 Simon Tatham.\n\n"
3585
3586         "Portions copyright Robert de Bath, Joris van Rantwijk, Delian "
3587         "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas "
3588         "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, "
3589         "Markus Kuhn, Colin Watson, Christopher Staite, and CORE SDI S.A.\n\n"
3590
3591         "Permission is hereby granted, free of charge, to any person "
3592         "obtaining a copy of this software and associated documentation "
3593         "files (the ""Software""), to deal in the Software without restriction, "
3594         "including without limitation the rights to use, copy, modify, merge, "
3595         "publish, distribute, sublicense, and/or sell copies of the Software, "
3596         "and to permit persons to whom the Software is furnished to do so, "
3597         "subject to the following conditions:\n\n"
3598
3599         "The above copyright notice and this permission notice shall be "
3600         "included in all copies or substantial portions of the Software.\n\n"
3601
3602         "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT "
3603         "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, "
3604         "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
3605         "MERCHANTABILITY, FITNESS FOR A PARTICULAR "
3606         "PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE "
3607         "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES "
3608         "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, "
3609         "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN "
3610         "CONNECTION WITH THE SOFTWARE OR THE USE OR "
3611         "OTHER DEALINGS IN THE SOFTWARE.";
3612
3613     title = dupcat(appname, " Licence", NULL);
3614     assert(aboutbox != NULL);
3615     messagebox(aboutbox, title, licence,
3616                string_width("LONGISH LINE OF TEXT SO THE LICENCE"
3617                             " BOX ISN'T EXCESSIVELY TALL AND THIN"),
3618                "OK", 'o', 1, 1, NULL);
3619     sfree(title);
3620 }
3621
3622 void about_box(void *window)
3623 {
3624     GtkWidget *w;
3625     GtkBox *action_area;
3626     char *title;
3627
3628     if (aboutbox) {
3629         gtk_widget_grab_focus(aboutbox);
3630         return;
3631     }
3632
3633     aboutbox = our_dialog_new();
3634     gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10);
3635     title = dupcat("About ", appname, NULL);
3636     gtk_window_set_title(GTK_WINDOW(aboutbox), title);
3637     sfree(title);
3638
3639     w = gtk_button_new_with_label("Close");
3640     gtk_widget_set_can_default(w, TRUE);
3641     gtk_window_set_default(GTK_WINDOW(aboutbox), w);
3642     action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox));
3643     gtk_box_pack_end(action_area, w, FALSE, FALSE, 0);
3644     g_signal_connect(G_OBJECT(w), "clicked",
3645                      G_CALLBACK(about_close_clicked), NULL);
3646     gtk_widget_show(w);
3647
3648     w = gtk_button_new_with_label("View Licence");
3649     gtk_widget_set_can_default(w, TRUE);
3650     gtk_box_pack_end(action_area, w, FALSE, FALSE, 0);
3651     g_signal_connect(G_OBJECT(w), "clicked",
3652                      G_CALLBACK(licence_clicked), NULL);
3653     gtk_widget_show(w);
3654
3655     w = gtk_label_new(appname);
3656     our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, FALSE, FALSE, 0);
3657     gtk_widget_show(w);
3658
3659     w = gtk_label_new(ver);
3660     our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, FALSE, FALSE, 5);
3661     gtk_widget_show(w);
3662
3663     w = gtk_label_new("Copyright 1997-2015 Simon Tatham. All rights reserved");
3664     our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, FALSE, FALSE, 5);
3665     gtk_widget_show(w);
3666
3667     set_transient_window_pos(GTK_WIDGET(window), aboutbox);
3668     gtk_window_set_transient_for(GTK_WINDOW(aboutbox),
3669                                  GTK_WINDOW(window));
3670     gtk_widget_show(aboutbox);
3671 }
3672
3673 struct eventlog_stuff {
3674     GtkWidget *parentwin, *window;
3675     struct controlbox *eventbox;
3676     struct Shortcuts scs;
3677     struct dlgparam dp;
3678     union control *listctrl;
3679     char **events;
3680     int nevents, negsize;
3681     char *seldata;
3682     int sellen;
3683     int ignore_selchange;
3684 };
3685
3686 static void eventlog_destroy(GtkWidget *widget, gpointer data)
3687 {
3688     struct eventlog_stuff *es = (struct eventlog_stuff *)data;
3689
3690     es->window = NULL;
3691     sfree(es->seldata);
3692     es->seldata = NULL;
3693     dlg_cleanup(&es->dp);
3694     ctrl_free_box(es->eventbox);
3695 }
3696 static void eventlog_ok_handler(union control *ctrl, void *dlg,
3697                                 void *data, int event)
3698 {
3699     if (event == EVENT_ACTION)
3700         dlg_end(dlg, 0);
3701 }
3702 static void eventlog_list_handler(union control *ctrl, void *dlg,
3703                                   void *data, int event)
3704 {
3705     struct eventlog_stuff *es = (struct eventlog_stuff *)data;
3706
3707     if (event == EVENT_REFRESH) {
3708         int i;
3709
3710         dlg_update_start(ctrl, dlg);
3711         dlg_listbox_clear(ctrl, dlg);
3712         for (i = 0; i < es->nevents; i++) {
3713             dlg_listbox_add(ctrl, dlg, es->events[i]);
3714         }
3715         dlg_update_done(ctrl, dlg);
3716     } else if (event == EVENT_SELCHANGE) {
3717         int i;
3718         int selsize = 0;
3719
3720         /*
3721          * If this SELCHANGE event is happening as a result of
3722          * deliberate deselection because someone else has grabbed
3723          * the selection, the last thing we want to do is pre-empt
3724          * them.
3725          */
3726         if (es->ignore_selchange)
3727             return;
3728
3729         /*
3730          * Construct the data to use as the selection.
3731          */
3732         sfree(es->seldata);
3733         es->seldata = NULL;
3734         es->sellen = 0;
3735         for (i = 0; i < es->nevents; i++) {
3736             if (dlg_listbox_issel(ctrl, dlg, i)) {
3737                 int extralen = strlen(es->events[i]);
3738
3739                 if (es->sellen + extralen + 2 > selsize) {
3740                     selsize = es->sellen + extralen + 512;
3741                     es->seldata = sresize(es->seldata, selsize, char);
3742                 }
3743
3744                 strcpy(es->seldata + es->sellen, es->events[i]);
3745                 es->sellen += extralen;
3746                 es->seldata[es->sellen++] = '\n';
3747             }
3748         }
3749
3750         if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY,
3751                                     GDK_CURRENT_TIME)) {
3752             extern GdkAtom compound_text_atom;
3753
3754             gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
3755                                      GDK_SELECTION_TYPE_STRING, 1);
3756             gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
3757                                      compound_text_atom, 1);
3758         }
3759
3760     }
3761 }
3762
3763 void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata,
3764                             guint info, guint time_stamp, gpointer data)
3765 {
3766     struct eventlog_stuff *es = (struct eventlog_stuff *)data;
3767
3768     gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8,
3769                            (unsigned char *)es->seldata, es->sellen);
3770 }
3771
3772 gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
3773                               gpointer data)
3774 {
3775     struct eventlog_stuff *es = (struct eventlog_stuff *)data;
3776     struct uctrl *uc;
3777
3778     /*
3779      * Deselect everything in the list box.
3780      */
3781     uc = dlg_find_byctrl(&es->dp, es->listctrl);
3782     es->ignore_selchange = 1;
3783 #if !GTK_CHECK_VERSION(2,0,0)
3784     assert(uc->list);
3785     gtk_list_unselect_all(GTK_LIST(uc->list));
3786 #else
3787     assert(uc->treeview);
3788     gtk_tree_selection_unselect_all
3789         (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)));
3790 #endif
3791     es->ignore_selchange = 0;
3792
3793     sfree(es->seldata);
3794     es->sellen = 0;
3795     es->seldata = NULL;
3796     return TRUE;
3797 }
3798
3799 void showeventlog(void *estuff, void *parentwin)
3800 {
3801     struct eventlog_stuff *es = (struct eventlog_stuff *)estuff;
3802     GtkWidget *window, *w0, *w1;
3803     GtkWidget *parent = GTK_WIDGET(parentwin);
3804     struct controlset *s0, *s1;
3805     union control *c;
3806     int index;
3807     char *title;
3808
3809     if (es->window) {
3810         gtk_widget_grab_focus(es->window);
3811         return;
3812     }
3813
3814     dlg_init(&es->dp);
3815
3816     for (index = 0; index < lenof(es->scs.sc); index++) {
3817         es->scs.sc[index].action = SHORTCUT_EMPTY;
3818     }
3819
3820     es->eventbox = ctrl_new_box();
3821
3822     s0 = ctrl_getset(es->eventbox, "", "", "");
3823     ctrl_columns(s0, 3, 33, 34, 33);
3824     c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help),
3825                         eventlog_ok_handler, P(NULL));
3826     c->button.column = 1;
3827     c->button.isdefault = TRUE;
3828
3829     s1 = ctrl_getset(es->eventbox, "x", "", "");
3830     es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help),
3831                                     eventlog_list_handler, P(es));
3832     c->listbox.height = 10;
3833     c->listbox.multisel = 2;
3834     c->listbox.ncols = 3;
3835     c->listbox.percentages = snewn(3, int);
3836     c->listbox.percentages[0] = 25;
3837     c->listbox.percentages[1] = 10;
3838     c->listbox.percentages[2] = 65;
3839
3840     es->window = window = our_dialog_new();
3841     title = dupcat(appname, " Event Log", NULL);
3842     gtk_window_set_title(GTK_WINDOW(window), title);
3843     sfree(title);
3844     w0 = layout_ctrls(&es->dp, &es->scs, s0, GTK_WINDOW(window));
3845     our_dialog_set_action_area(GTK_WINDOW(window), w0);
3846     gtk_widget_show(w0);
3847     w1 = layout_ctrls(&es->dp, &es->scs, s1, GTK_WINDOW(window));
3848     gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
3849     gtk_widget_set_size_request(w1, 20 + string_width
3850                                 ("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS "
3851                                  "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"),
3852                                 -1);
3853     our_dialog_add_to_content_area(GTK_WINDOW(window), w1, TRUE, TRUE, 0);
3854     gtk_widget_show(w1);
3855
3856     es->dp.data = es;
3857     es->dp.shortcuts = &es->scs;
3858     es->dp.lastfocus = NULL;
3859     es->dp.retval = 0;
3860     es->dp.window = window;
3861
3862     dlg_refresh(NULL, &es->dp);
3863
3864     if (parent) {
3865         set_transient_window_pos(parent, window);
3866         gtk_window_set_transient_for(GTK_WINDOW(window),
3867                                      GTK_WINDOW(parent));
3868     } else
3869         gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
3870     gtk_widget_show(window);
3871
3872     g_signal_connect(G_OBJECT(window), "destroy",
3873                      G_CALLBACK(eventlog_destroy), es);
3874     g_signal_connect(G_OBJECT(window), "key_press_event",
3875                      G_CALLBACK(win_key_press), &es->dp);
3876     g_signal_connect(G_OBJECT(window), "selection_get",
3877                      G_CALLBACK(eventlog_selection_get), es);
3878     g_signal_connect(G_OBJECT(window), "selection_clear_event",
3879                      G_CALLBACK(eventlog_selection_clear), es);
3880 }
3881
3882 void *eventlogstuff_new(void)
3883 {
3884     struct eventlog_stuff *es;
3885     es = snew(struct eventlog_stuff);
3886     memset(es, 0, sizeof(*es));
3887     return es;
3888 }
3889
3890 void logevent_dlg(void *estuff, const char *string)
3891 {
3892     struct eventlog_stuff *es = (struct eventlog_stuff *)estuff;
3893
3894     char timebuf[40];
3895     struct tm tm;
3896
3897     if (es->nevents >= es->negsize) {
3898         es->negsize += 64;
3899         es->events = sresize(es->events, es->negsize, char *);
3900     }
3901
3902     tm=ltime();
3903     strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
3904
3905     es->events[es->nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
3906     strcpy(es->events[es->nevents], timebuf);
3907     strcat(es->events[es->nevents], string);
3908     if (es->window) {
3909         dlg_listbox_add(es->listctrl, &es->dp, es->events[es->nevents]);
3910     }
3911     es->nevents++;
3912 }
3913
3914 int askappend(void *frontend, Filename *filename,
3915               void (*callback)(void *ctx, int result), void *ctx)
3916 {
3917     static const char msgtemplate[] =
3918         "The session log file \"%.*s\" already exists. "
3919         "You can overwrite it with a new session log, "
3920         "append your session log to the end of it, "
3921         "or disable session logging for this session.";
3922     char *message;
3923     char *mbtitle;
3924     int mbret;
3925
3926     message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
3927     mbtitle = dupprintf("%s Log to File", appname);
3928
3929     mbret = messagebox(get_window(frontend), mbtitle, message,
3930                        string_width("LINE OF TEXT SUITABLE FOR THE"
3931                                     " ASKAPPEND WIDTH"),
3932                        "Overwrite", 'o', 1, 2,
3933                        "Append", 'a', 0, 1,
3934                        "Disable", 'd', -1, 0,
3935                        NULL);
3936
3937     sfree(message);
3938     sfree(mbtitle);
3939
3940     return mbret;
3941 }