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