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