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