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