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