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