]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkdlg.c
Further work on the GTK config box; mostly implemented keyboard
[PuTTY.git] / unix / gtkdlg.c
1 /*
2  * gtkdlg.c - GTK implementation of the PuTTY configuration box.
3  */
4
5 /*
6  * TODO:
7  * 
8  *  - keyboard shortcuts on list boxes and the treeview
9  *     + find the currently selected item and focus it?
10  *     + what if there are many selected items, or none?
11  *     + none of this is any use unless Up and Down are made to do
12  *       something plausibly sane anyway
13  * 
14  *  - focus stuff
15  *     + `last focused' for nasty sessionsaver hack
16  *     + set focus into a sensible control to start with
17  *     + perhaps we need uc->primary as the right widget to
18  *       forcibly focus?
19  *        * no, because I think the right widget to focus with
20  *          radio buttons is the currently selected one. Hmm.
21  * 
22  *  - dlg_error_msg
23  * 
24  *  - must return a value from the dialog box!
25  *     + easy, just put it in dp.
26  * 
27  *  - font selection hiccup: the default `fixed' is not
28  *    automatically translated into its expanded XLFD form when the
29  *    font selector is started. It should be.
30  * 
31  *  - cosmetics:
32  *     + can't we _somehow_ have less leading between radio buttons?
33  *     + wrapping text widgets, the horror, the horror
34  *     + labels and their associated edit boxes don't line up
35  *       properly
36  *     + IWBNI arrows in list boxes didn't do such a bloody silly
37  *       thing (moving focus without moving selection, and
38  *       furthermore not feeding back to the scrollbar). And arrows
39  *       in the treeview doubly so. Gah.
40  *     + don't suppose we can fix the vertical offset labels get
41  *       from their underlines?
42  *     + why the hell are the Up/Down focus movement keys sorting
43  *       things by _width_? (See the Logging and Features panels
44  *       for good examples.)
45  */
46
47 /*
48  * TODO when porting to GTK 2.0:
49  * 
50  *  - GtkTree is apparently deprecated and we should switch to
51  *    GtkTreeView instead.
52  *  - GtkLabel has a built-in mnemonic scheme, so we should at
53  *    least consider switching to that from the current adhockery.
54  */
55
56 #include <assert.h>
57 #include <ctype.h>
58 #include <gtk/gtk.h>
59 #include <gdk/gdkkeysyms.h>
60
61 #include "gtkcols.h"
62 #include "gtkpanel.h"
63
64 #ifdef TESTMODE
65 #define PUTTY_DO_GLOBALS               /* actually _define_ globals */
66 #endif
67
68 #include "putty.h"
69 #include "dialog.h"
70 #include "tree234.h"
71
72 struct Shortcut {
73     GtkWidget *widget;
74     struct uctrl *uc;
75     int action;
76 };
77
78 struct Shortcuts {
79     struct Shortcut sc[128];
80 };
81
82 struct uctrl {
83     union control *ctrl;
84     GtkWidget *toplevel;
85     void *privdata;
86     int privdata_needs_free;
87     GtkWidget **buttons; int nbuttons; /* for radio buttons */
88     GtkWidget *entry;         /* for editbox, combobox, filesel, fontsel */
89     GtkWidget *button;        /* for filesel, fontsel */
90     GtkWidget *list;          /* for combobox, listbox */
91     GtkWidget *menu;          /* for optionmenu (==droplist) */
92     GtkWidget *optmenu;       /* also for optionmenu */
93     GtkWidget *text;          /* for text */
94 };
95
96 struct dlgparam {
97     tree234 *byctrl, *bywidget;
98     void *data;
99     struct { unsigned char r, g, b, ok; } coloursel_result;   /* 0-255 */
100     /* `flags' are set to indicate when a GTK signal handler is being called
101      * due to automatic processing and should not flag a user event. */
102     int flags;
103     struct Shortcuts *shortcuts;
104 };
105 #define FLAG_UPDATING_COMBO_LIST 1
106
107 enum {                                 /* values for Shortcut.action */
108     SHORTCUT_EMPTY,                    /* no shortcut on this key */
109     SHORTCUT_FOCUS,                    /* focus the supplied widget */
110     SHORTCUT_UCTRL,                    /* do something sane with uctrl */
111     SHORTCUT_UCTRL_UP,                 /* uctrl is a draglist, move Up */
112     SHORTCUT_UCTRL_DOWN,               /* uctrl is a draglist, move Down */
113 };
114
115 /*
116  * Forward references.
117  */
118 static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
119                          int chr, int action, void *ptr);
120 static void listitem_button(GtkWidget *item, GdkEventButton *event,
121                             gpointer data);
122 static void menuitem_activate(GtkMenuItem *item, gpointer data);
123 static void coloursel_ok(GtkButton *button, gpointer data);
124 static void coloursel_cancel(GtkButton *button, gpointer data);
125
126 static int uctrl_cmp_byctrl(void *av, void *bv)
127 {
128     struct uctrl *a = (struct uctrl *)av;
129     struct uctrl *b = (struct uctrl *)bv;
130     if (a->ctrl < b->ctrl)
131         return -1;
132     else if (a->ctrl > b->ctrl)
133         return +1;
134     return 0;
135 }
136
137 static int uctrl_cmp_byctrl_find(void *av, void *bv)
138 {
139     union control *a = (union control *)av;
140     struct uctrl *b = (struct uctrl *)bv;
141     if (a < b->ctrl)
142         return -1;
143     else if (a > b->ctrl)
144         return +1;
145     return 0;
146 }
147
148 static int uctrl_cmp_bywidget(void *av, void *bv)
149 {
150     struct uctrl *a = (struct uctrl *)av;
151     struct uctrl *b = (struct uctrl *)bv;
152     if (a->toplevel < b->toplevel)
153         return -1;
154     else if (a->toplevel > b->toplevel)
155         return +1;
156     return 0;
157 }
158
159 static int uctrl_cmp_bywidget_find(void *av, void *bv)
160 {
161     GtkWidget *a = (GtkWidget *)av;
162     struct uctrl *b = (struct uctrl *)bv;
163     if (a < b->toplevel)
164         return -1;
165     else if (a > b->toplevel)
166         return +1;
167     return 0;
168 }
169
170 static void dlg_init(struct dlgparam *dp)
171 {
172     dp->byctrl = newtree234(uctrl_cmp_byctrl);
173     dp->bywidget = newtree234(uctrl_cmp_bywidget);
174     dp->coloursel_result.ok = FALSE;
175 }
176
177 static void dlg_cleanup(struct dlgparam *dp)
178 {
179     struct uctrl *uc;
180
181     freetree234(dp->byctrl);           /* doesn't free the uctrls inside */
182     while ( (uc = index234(dp->bywidget, 0)) != NULL) {
183         del234(dp->bywidget, uc);
184         if (uc->privdata_needs_free)
185             sfree(uc->privdata);
186         sfree(uc->buttons);
187         sfree(uc);
188     }
189     freetree234(dp->bywidget);
190 }
191
192 static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc)
193 {
194     add234(dp->byctrl, uc);
195     add234(dp->bywidget, uc);
196 }
197
198 static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, union control *ctrl)
199 {
200     return find234(dp->byctrl, ctrl, uctrl_cmp_byctrl_find);
201 }
202
203 static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w)
204 {
205     struct uctrl *ret = NULL;
206     do {
207         ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find);
208         if (ret)
209             return ret;
210         w = w->parent;
211     } while (w);
212     return ret;
213 }
214
215 void *dlg_get_privdata(union control *ctrl, void *dlg)
216 {
217     struct dlgparam *dp = (struct dlgparam *)dlg;
218     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
219     return uc->privdata;
220 }
221
222 void dlg_set_privdata(union control *ctrl, void *dlg, void *ptr)
223 {
224     struct dlgparam *dp = (struct dlgparam *)dlg;
225     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
226     uc->privdata = ptr;
227     uc->privdata_needs_free = FALSE;
228 }
229
230 void *dlg_alloc_privdata(union control *ctrl, void *dlg, size_t size)
231 {
232     struct dlgparam *dp = (struct dlgparam *)dlg;
233     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
234     uc->privdata = smalloc(size);
235     uc->privdata_needs_free = FALSE;
236     return uc->privdata;
237 }
238
239 union control *dlg_last_focused(void *dlg)
240 {
241     struct dlgparam *dp = (struct dlgparam *)dlg;
242     return NULL;                       /* FIXME */
243 }
244
245 void dlg_radiobutton_set(union control *ctrl, void *dlg, int which)
246 {
247     struct dlgparam *dp = (struct dlgparam *)dlg;
248     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
249     assert(uc->ctrl->generic.type == CTRL_RADIO);
250     assert(uc->buttons != NULL);
251     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), TRUE);
252 }
253
254 int dlg_radiobutton_get(union control *ctrl, void *dlg)
255 {
256     struct dlgparam *dp = (struct dlgparam *)dlg;
257     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
258     int i;
259
260     assert(uc->ctrl->generic.type == CTRL_RADIO);
261     assert(uc->buttons != NULL);
262     for (i = 0; i < uc->nbuttons; i++)
263         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->buttons[i])))
264             return i;
265     return 0;                          /* got to return something */
266 }
267
268 void dlg_checkbox_set(union control *ctrl, void *dlg, int checked)
269 {
270     struct dlgparam *dp = (struct dlgparam *)dlg;
271     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
272     assert(uc->ctrl->generic.type == CTRL_CHECKBOX);
273     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked);
274 }
275
276 int dlg_checkbox_get(union control *ctrl, void *dlg)
277 {
278     struct dlgparam *dp = (struct dlgparam *)dlg;
279     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
280     assert(uc->ctrl->generic.type == CTRL_CHECKBOX);
281     return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel));
282 }
283
284 void dlg_editbox_set(union control *ctrl, void *dlg, char const *text)
285 {
286     struct dlgparam *dp = (struct dlgparam *)dlg;
287     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
288     assert(uc->ctrl->generic.type == CTRL_EDITBOX);
289     assert(uc->entry != NULL);
290     gtk_entry_set_text(GTK_ENTRY(uc->entry), text);
291 }
292
293 void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length)
294 {
295     struct dlgparam *dp = (struct dlgparam *)dlg;
296     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
297     assert(uc->ctrl->generic.type == CTRL_EDITBOX);
298     assert(uc->entry != NULL);
299     strncpy(buffer, gtk_entry_get_text(GTK_ENTRY(uc->entry)),
300             length);
301     buffer[length-1] = '\0';
302 }
303
304 static void container_remove_and_destroy(GtkWidget *w, gpointer data)
305 {
306     GtkContainer *cont = GTK_CONTAINER(data);
307     /* gtk_container_remove will unref the widget for us; we need not. */
308     gtk_container_remove(cont, w);
309 }
310
311 /* The `listbox' functions can also apply to combo boxes. */
312 void dlg_listbox_clear(union control *ctrl, void *dlg)
313 {
314     struct dlgparam *dp = (struct dlgparam *)dlg;
315     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
316     GtkContainer *cont;
317
318     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
319            uc->ctrl->generic.type == CTRL_LISTBOX);
320     assert(uc->menu != NULL || uc->list != NULL);
321
322     cont = (uc->menu ? GTK_CONTAINER(uc->menu) : GTK_CONTAINER(uc->list));
323
324     gtk_container_foreach(cont, container_remove_and_destroy, cont);
325 }
326
327 void dlg_listbox_del(union control *ctrl, void *dlg, int index)
328 {
329     struct dlgparam *dp = (struct dlgparam *)dlg;
330     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
331
332     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
333            uc->ctrl->generic.type == CTRL_LISTBOX);
334     assert(uc->menu != NULL || uc->list != NULL);
335
336     if (uc->menu) {
337         gtk_container_remove
338             (GTK_CONTAINER(uc->menu),
339              g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index));
340     } else {
341         gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
342     }
343 }
344
345 void dlg_listbox_add(union control *ctrl, void *dlg, char const *text)
346 {
347     dlg_listbox_addwithindex(ctrl, dlg, text, 0);
348 }
349
350 /*
351  * Each listbox entry may have a numeric id associated with it.
352  * Note that some front ends only permit a string to be stored at
353  * each position, which means that _if_ you put two identical
354  * strings in any listbox then you MUST not assign them different
355  * IDs and expect to get meaningful results back.
356  */
357 void dlg_listbox_addwithindex(union control *ctrl, void *dlg,
358                               char const *text, int id)
359 {
360     struct dlgparam *dp = (struct dlgparam *)dlg;
361     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
362
363     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
364            uc->ctrl->generic.type == CTRL_LISTBOX);
365     assert(uc->menu != NULL || uc->list != NULL);
366
367     dp->flags |= FLAG_UPDATING_COMBO_LIST;
368
369     if (uc->menu) {
370         /*
371          * List item in a drop-down (but non-combo) list. Tabs are
372          * ignored; we just provide a standard menu item with the
373          * text.
374          */
375         GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
376
377         gtk_container_add(GTK_CONTAINER(uc->menu), menuitem);
378         gtk_widget_show(menuitem);
379
380         gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", (gpointer)id);
381         gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
382                            GTK_SIGNAL_FUNC(menuitem_activate), dp);
383     } else if (!uc->entry) {
384         /*
385          * List item in a non-combo-box list box. We make all of
386          * these Columns containing GtkLabels. This allows us to do
387          * the nasty force_left hack irrespective of whether there
388          * are tabs in the thing.
389          */
390         GtkWidget *listitem = gtk_list_item_new();
391         GtkWidget *cols = columns_new(10);
392         gint *percents;
393         int i, ncols;
394
395         /* Count the tabs in the text, and hence determine # of columns. */
396         ncols = 1;
397         for (i = 0; text[i]; i++)
398             if (text[i] == '\t')
399                 ncols++;
400
401         assert(ncols <=
402                (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1));
403         percents = smalloc(ncols * sizeof(gint));
404         percents[ncols-1] = 100;
405         for (i = 0; i < ncols-1; i++) {
406             percents[i] = uc->ctrl->listbox.percentages[i];
407             percents[ncols-1] -= percents[i];
408         }
409         columns_set_cols(COLUMNS(cols), ncols, percents);
410         sfree(percents);
411
412         for (i = 0; i < ncols; i++) {
413             int len = strcspn(text, "\t");
414             char *dup = dupprintf("%.*s", len, text);
415             GtkWidget *label;
416
417             text += len;
418             if (*text) text++;
419             label = gtk_label_new(dup);
420             sfree(dup);
421
422             columns_add(COLUMNS(cols), label, i, 1);
423             columns_force_left_align(COLUMNS(cols), label);
424             gtk_widget_show(label);
425         }
426         gtk_container_add(GTK_CONTAINER(listitem), cols);
427         gtk_widget_show(cols);
428         gtk_container_add(GTK_CONTAINER(uc->list), listitem);
429         gtk_widget_show(listitem);
430
431         gtk_signal_connect(GTK_OBJECT(listitem), "button_press_event",
432                            GTK_SIGNAL_FUNC(listitem_button), dp);
433         gtk_object_set_data(GTK_OBJECT(listitem), "user-data", (gpointer)id);
434     } else {
435         /*
436          * List item in a combo-box list, which means the sensible
437          * thing to do is make it a perfectly normal label. Hence
438          * tabs are disregarded.
439          */
440         GtkWidget *listitem = gtk_list_item_new_with_label(text);
441
442         gtk_container_add(GTK_CONTAINER(uc->list), listitem);
443         gtk_widget_show(listitem);
444
445         gtk_object_set_data(GTK_OBJECT(listitem), "user-data", (gpointer)id);
446     }
447
448     dp->flags &= ~FLAG_UPDATING_COMBO_LIST;
449 }
450
451 int dlg_listbox_getid(union control *ctrl, void *dlg, int index)
452 {
453     struct dlgparam *dp = (struct dlgparam *)dlg;
454     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
455     GList *children;
456     GtkObject *item;
457
458     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
459            uc->ctrl->generic.type == CTRL_LISTBOX);
460     assert(uc->menu != NULL || uc->list != NULL);
461
462     children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
463                                                     uc->list));
464     item = GTK_OBJECT(g_list_nth_data(children, index));
465
466     return (int)gtk_object_get_data(GTK_OBJECT(item), "user-data");
467 }
468
469 /* dlg_listbox_index returns <0 if no single element is selected. */
470 int dlg_listbox_index(union control *ctrl, void *dlg)
471 {
472     struct dlgparam *dp = (struct dlgparam *)dlg;
473     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
474     GList *children;
475     GtkWidget *item, *activeitem;
476     int i;
477     int selected = -1;
478
479     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
480            uc->ctrl->generic.type == CTRL_LISTBOX);
481     assert(uc->menu != NULL || uc->list != NULL);
482
483     if (uc->menu)
484         activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
485
486     children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
487                                                     uc->list));
488     for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL;
489          i++, children = children->next) {
490         if (uc->menu ? activeitem == item :
491             GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) {
492             if (selected < 0)
493                 selected = i;
494             else
495                 return -1;
496         }
497     }
498     return selected;
499 }
500
501 int dlg_listbox_issel(union control *ctrl, void *dlg, int index)
502 {
503     struct dlgparam *dp = (struct dlgparam *)dlg;
504     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
505     GList *children;
506     GtkWidget *item, *activeitem;
507
508     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
509            uc->ctrl->generic.type == CTRL_LISTBOX);
510     assert(uc->menu != NULL || uc->list != NULL);
511
512     children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
513                                                     uc->list));
514     item = GTK_WIDGET(g_list_nth_data(children, index));
515
516     if (uc->menu) {
517         activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
518         return item == activeitem;
519     } else {
520         return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED;
521     }
522 }
523
524 void dlg_listbox_select(union control *ctrl, void *dlg, int index)
525 {
526     struct dlgparam *dp = (struct dlgparam *)dlg;
527     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
528
529     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
530            uc->ctrl->generic.type == CTRL_LISTBOX);
531     assert(uc->optmenu != NULL || uc->list != NULL);
532
533     if (uc->optmenu) {
534         gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index);
535     } else {
536         gtk_list_select_item(GTK_LIST(uc->list), index);
537     }
538 }
539
540 void dlg_text_set(union control *ctrl, void *dlg, char const *text)
541 {
542     struct dlgparam *dp = (struct dlgparam *)dlg;
543     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
544
545     assert(uc->ctrl->generic.type == CTRL_TEXT);
546     assert(uc->text != NULL);
547
548     gtk_label_set_text(GTK_LABEL(uc->text), text);
549 }
550
551 void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn)
552 {
553     struct dlgparam *dp = (struct dlgparam *)dlg;
554     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
555     assert(uc->ctrl->generic.type == CTRL_FILESELECT);
556     assert(uc->entry != NULL);
557     gtk_entry_set_text(GTK_ENTRY(uc->entry), fn.path);
558 }
559
560 void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn)
561 {
562     struct dlgparam *dp = (struct dlgparam *)dlg;
563     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
564     assert(uc->ctrl->generic.type == CTRL_FILESELECT);
565     assert(uc->entry != NULL);
566     strncpy(fn->path, gtk_entry_get_text(GTK_ENTRY(uc->entry)),
567             lenof(fn->path));
568     fn->path[lenof(fn->path)-1] = '\0';
569 }
570
571 void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs)
572 {
573     struct dlgparam *dp = (struct dlgparam *)dlg;
574     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
575     assert(uc->ctrl->generic.type == CTRL_FONTSELECT);
576     assert(uc->entry != NULL);
577     gtk_entry_set_text(GTK_ENTRY(uc->entry), fs.name);
578 }
579
580 void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs)
581 {
582     struct dlgparam *dp = (struct dlgparam *)dlg;
583     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
584     assert(uc->ctrl->generic.type == CTRL_FONTSELECT);
585     assert(uc->entry != NULL);
586     strncpy(fs->name, gtk_entry_get_text(GTK_ENTRY(uc->entry)),
587             lenof(fs->name));
588     fs->name[lenof(fs->name)-1] = '\0';
589 }
590
591 /*
592  * Bracketing a large set of updates in these two functions will
593  * cause the front end (if possible) to delay updating the screen
594  * until it's all complete, thus avoiding flicker.
595  */
596 void dlg_update_start(union control *ctrl, void *dlg)
597 {
598     /*
599      * Apparently we can't do this at all in GTK. GtkCList supports
600      * freeze and thaw, but not GtkList. Bah.
601      */
602 }
603
604 void dlg_update_done(union control *ctrl, void *dlg)
605 {
606     /*
607      * Apparently we can't do this at all in GTK. GtkCList supports
608      * freeze and thaw, but not GtkList. Bah.
609      */
610 }
611
612 void dlg_set_focus(union control *ctrl, void *dlg)
613 {
614     struct dlgparam *dp = (struct dlgparam *)dlg;
615     /* FIXME */
616 }
617
618 /*
619  * During event processing, you might well want to give an error
620  * indication to the user. dlg_beep() is a quick and easy generic
621  * error; dlg_error() puts up a message-box or equivalent.
622  */
623 void dlg_beep(void *dlg)
624 {
625     gdk_beep();
626 }
627
628 void dlg_error_msg(void *dlg, char *msg)
629 {
630     struct dlgparam *dp = (struct dlgparam *)dlg;
631     /* FIXME */
632 }
633
634 /*
635  * This function signals to the front end that the dialog's
636  * processing is completed, and passes an integer value (typically
637  * a success status).
638  */
639 void dlg_end(void *dlg, int value)
640 {
641     struct dlgparam *dp = (struct dlgparam *)dlg;
642     gtk_main_quit();
643     /* FIXME: don't forget to faff about with returning a value */
644 }
645
646 void dlg_refresh(union control *ctrl, void *dlg)
647 {
648     struct dlgparam *dp = (struct dlgparam *)dlg;
649     struct uctrl *uc;
650
651     if (ctrl) {
652         if (ctrl->generic.handler != NULL)
653             ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
654     } else {
655         int i;
656
657         for (i = 0; (uc = index234(dp->byctrl, i)) != NULL; i++) {
658             assert(uc->ctrl != NULL);
659             if (uc->ctrl->generic.handler != NULL)
660                 uc->ctrl->generic.handler(uc->ctrl, dp,
661                                           dp->data, EVENT_REFRESH);
662         }
663     }
664 }
665
666 void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b)
667 {
668     struct dlgparam *dp = (struct dlgparam *)dlg;
669     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
670     gdouble cvals[4];
671
672     GtkWidget *coloursel =
673         gtk_color_selection_dialog_new("Select a colour");
674     GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel);
675
676     dp->coloursel_result.ok = FALSE;
677
678     gtk_window_set_modal(GTK_WINDOW(coloursel), TRUE);
679     gtk_color_selection_set_opacity(GTK_COLOR_SELECTION(ccs->colorsel), FALSE);
680     cvals[0] = r / 255.0;
681     cvals[1] = g / 255.0;
682     cvals[2] = b / 255.0;
683     cvals[3] = 1.0;                    /* fully opaque! */
684     gtk_color_selection_set_color(GTK_COLOR_SELECTION(ccs->colorsel), cvals);
685
686     gtk_object_set_data(GTK_OBJECT(ccs->ok_button), "user-data",
687                         (gpointer)coloursel);
688     gtk_object_set_data(GTK_OBJECT(ccs->cancel_button), "user-data",
689                         (gpointer)coloursel);
690     gtk_object_set_data(GTK_OBJECT(coloursel), "user-data", (gpointer)uc);
691     gtk_signal_connect(GTK_OBJECT(ccs->ok_button), "clicked",
692                        GTK_SIGNAL_FUNC(coloursel_ok), (gpointer)dp);
693     gtk_signal_connect(GTK_OBJECT(ccs->cancel_button), "clicked",
694                        GTK_SIGNAL_FUNC(coloursel_cancel), (gpointer)dp);
695     gtk_signal_connect_object(GTK_OBJECT(ccs->ok_button), "clicked",
696                               GTK_SIGNAL_FUNC(gtk_widget_destroy),
697                               (gpointer)coloursel);
698     gtk_signal_connect_object(GTK_OBJECT(ccs->cancel_button), "clicked",
699                               GTK_SIGNAL_FUNC(gtk_widget_destroy),
700                               (gpointer)coloursel);
701     gtk_widget_show(coloursel);
702 }
703
704 int dlg_coloursel_results(union control *ctrl, void *dlg,
705                           int *r, int *g, int *b)
706 {
707     struct dlgparam *dp = (struct dlgparam *)dlg;
708     if (dp->coloursel_result.ok) {
709         *r = dp->coloursel_result.r;
710         *g = dp->coloursel_result.g;
711         *b = dp->coloursel_result.b;
712         return 1;
713     } else
714         return 0;
715 }
716
717 /* ----------------------------------------------------------------------
718  * Signal handlers while the dialog box is active.
719  */
720
721 static void button_clicked(GtkButton *button, gpointer data)
722 {
723     struct dlgparam *dp = (struct dlgparam *)data;
724     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
725     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
726 }
727
728 static void button_toggled(GtkToggleButton *tb, gpointer data)
729 {
730     struct dlgparam *dp = (struct dlgparam *)data;
731     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tb));
732     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
733 }
734
735 static int editbox_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
736 {
737     /*
738      * GtkEntry has a nasty habit of eating the Return key, which
739      * is unhelpful since it doesn't actually _do_ anything with it
740      * (it calls gtk_widget_activate, but our edit boxes never need
741      * activating). So I catch Return before GtkEntry sees it, and
742      * pass it straight on to the parent widget. Effect: hitting
743      * Return in an edit box will now activate the default button
744      * in the dialog just like it will everywhere else.
745      */
746     if (event->keyval == GDK_Return && widget->parent != NULL) {
747         gint return_val;
748         gtk_signal_emit_stop_by_name (GTK_OBJECT(widget), "key_press_event");
749         gtk_signal_emit_by_name(GTK_OBJECT(widget->parent), "key_press_event",
750                                 event, &return_val);
751         return return_val;
752     }
753     return FALSE;
754 }
755
756 static void editbox_changed(GtkEditable *ed, gpointer data)
757 {
758     struct dlgparam *dp = (struct dlgparam *)data;
759     if (!(dp->flags & FLAG_UPDATING_COMBO_LIST)) {
760         struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
761         uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
762     }
763 }
764
765 static void editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event,
766                               gpointer data)
767 {
768     struct dlgparam *dp = (struct dlgparam *)data;
769     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
770     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_REFRESH);
771 }
772
773 static void listitem_button(GtkWidget *item, GdkEventButton *event,
774                             gpointer data)
775 {
776     struct dlgparam *dp = (struct dlgparam *)data;
777     if (event->type == GDK_2BUTTON_PRESS ||
778         event->type == GDK_3BUTTON_PRESS) {
779         struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
780         uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
781     }
782 }
783
784 static void list_selchange(GtkList *list, gpointer data)
785 {
786     struct dlgparam *dp = (struct dlgparam *)data;
787     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list));
788     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
789 }
790
791 static void menuitem_activate(GtkMenuItem *item, gpointer data)
792 {
793     struct dlgparam *dp = (struct dlgparam *)data;
794     GtkWidget *menushell = GTK_WIDGET(item)->parent;
795     gpointer optmenu = gtk_object_get_data(GTK_OBJECT(menushell), "user-data");
796     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu));
797     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
798 }
799
800 static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction)
801 {
802     int index = dlg_listbox_index(uc->ctrl, dp);
803     GList *children = gtk_container_children(GTK_CONTAINER(uc->list));
804     GtkWidget *child;
805
806     if ((index < 0) ||
807         (index == 0 && direction < 0) ||
808         (index == g_list_length(children)-1 && direction > 0)) {
809         gdk_beep();
810         return;
811     }
812
813     child = g_list_nth_data(children, index);
814     gtk_widget_ref(child);
815     gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
816     children = NULL;
817     children = g_list_append(children, child);
818     gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction);
819     gtk_list_select_item(GTK_LIST(uc->list), index + direction);
820     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
821 }
822
823 static void draglist_up(GtkButton *button, gpointer data)
824 {
825     struct dlgparam *dp = (struct dlgparam *)data;
826     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
827     draglist_move(dp, uc, -1);
828 }
829
830 static void draglist_down(GtkButton *button, gpointer data)
831 {
832     struct dlgparam *dp = (struct dlgparam *)data;
833     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
834     draglist_move(dp, uc, +1);
835 }
836
837 static void filesel_ok(GtkButton *button, gpointer data)
838 {
839     struct dlgparam *dp = (struct dlgparam *)data;
840     gpointer filesel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
841     struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(filesel), "user-data");
842     char *name = gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel));
843     gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
844 }
845
846 static void fontsel_ok(GtkButton *button, gpointer data)
847 {
848     struct dlgparam *dp = (struct dlgparam *)data;
849     gpointer fontsel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
850     struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(fontsel), "user-data");
851     char *name = gtk_font_selection_dialog_get_font_name
852         (GTK_FONT_SELECTION_DIALOG(fontsel));
853     gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
854 }
855
856 static void coloursel_ok(GtkButton *button, gpointer data)
857 {
858     struct dlgparam *dp = (struct dlgparam *)data;
859     gpointer coloursel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
860     struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(coloursel), "user-data");
861     gdouble cvals[4];
862     gtk_color_selection_get_color
863         (GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(coloursel)->colorsel),
864          cvals);
865     dp->coloursel_result.r = (int) (255 * cvals[0]);
866     dp->coloursel_result.g = (int) (255 * cvals[1]);
867     dp->coloursel_result.b = (int) (255 * cvals[2]);
868     dp->coloursel_result.ok = TRUE;
869     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
870 }
871
872 static void coloursel_cancel(GtkButton *button, gpointer data)
873 {
874     struct dlgparam *dp = (struct dlgparam *)data;
875     gpointer coloursel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
876     struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(coloursel), "user-data");
877     dp->coloursel_result.ok = FALSE;
878     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
879 }
880
881 static void filefont_clicked(GtkButton *button, gpointer data)
882 {
883     struct dlgparam *dp = (struct dlgparam *)data;
884     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
885
886     if (uc->ctrl->generic.type == CTRL_FILESELECT) {
887         GtkWidget *filesel =
888             gtk_file_selection_new(uc->ctrl->fileselect.title);
889         gtk_window_set_modal(GTK_WINDOW(filesel), TRUE);
890         gtk_object_set_data
891             (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
892              (gpointer)filesel);
893         gtk_object_set_data(GTK_OBJECT(filesel), "user-data", (gpointer)uc);
894         gtk_signal_connect
895             (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
896              GTK_SIGNAL_FUNC(filesel_ok), (gpointer)dp);
897         gtk_signal_connect_object
898             (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
899              GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
900         gtk_signal_connect_object
901             (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
902              GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
903         gtk_widget_show(filesel);
904     }
905
906     if (uc->ctrl->generic.type == CTRL_FONTSELECT) {
907         gchar *spacings[] = { "c", "m", NULL };
908         GtkWidget *fontsel =
909             gtk_font_selection_dialog_new("Select a font");
910         gtk_window_set_modal(GTK_WINDOW(fontsel), TRUE);
911         gtk_font_selection_dialog_set_filter
912             (GTK_FONT_SELECTION_DIALOG(fontsel),
913              GTK_FONT_FILTER_BASE, GTK_FONT_ALL,
914              NULL, NULL, NULL, NULL, spacings, NULL);
915         gtk_font_selection_dialog_set_font_name
916             (GTK_FONT_SELECTION_DIALOG(fontsel),
917              gtk_entry_get_text(GTK_ENTRY(uc->entry)));
918         gtk_object_set_data
919             (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
920              "user-data", (gpointer)fontsel);
921         gtk_object_set_data(GTK_OBJECT(fontsel), "user-data", (gpointer)uc);
922         gtk_signal_connect
923             (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
924              "clicked", GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp);
925         gtk_signal_connect_object
926             (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
927              "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy),
928              (gpointer)fontsel);
929         gtk_signal_connect_object
930             (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button),
931              "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy),
932              (gpointer)fontsel);
933         gtk_widget_show(fontsel);
934     }
935 }
936
937 /* ----------------------------------------------------------------------
938  * This function does the main layout work: it reads a controlset,
939  * it creates the relevant GTK controls, and returns a GtkWidget
940  * containing the result. (This widget might be a title of some
941  * sort, it might be a Columns containing many controls, or it
942  * might be a GtkFrame containing a Columns; whatever it is, it's
943  * definitely a GtkWidget and should probably be added to a
944  * GtkVbox.)
945  * 
946  * `listitemheight' is used to calculate a usize for list boxes: it
947  * should be the height from the size request of a GtkListItem.
948  * 
949  * `win' is required for setting the default button. If it is
950  * non-NULL, all buttons created will be default-capable (so they
951  * have extra space round them for the default highlight).
952  */
953 GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
954                         struct controlset *s, int listitemheight,
955                         GtkWindow *win)
956 {
957     Columns *cols;
958     GtkWidget *ret;
959     int i;
960
961     if (!s->boxname && s->boxtitle) {
962         /* This controlset is a panel title. */
963         return gtk_label_new(s->boxtitle);
964     }
965
966     /*
967      * Otherwise, we expect to be laying out actual controls, so
968      * we'll start by creating a Columns for the purpose.
969      */
970     cols = COLUMNS(columns_new(4));
971     ret = GTK_WIDGET(cols);
972     gtk_widget_show(ret);
973
974     /*
975      * Create a containing frame if we have a box name.
976      */
977     if (*s->boxname) {
978         ret = gtk_frame_new(s->boxtitle);   /* NULL is valid here */
979         gtk_container_set_border_width(GTK_CONTAINER(cols), 4);
980         gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols));
981         gtk_widget_show(ret);
982     }
983
984     /*
985      * Now iterate through the controls themselves, create them,
986      * and add them to the Columns.
987      */
988     for (i = 0; i < s->ncontrols; i++) {
989         union control *ctrl = s->ctrls[i];
990         struct uctrl *uc;
991         int left = FALSE;
992         GtkWidget *w = NULL;
993
994         switch (ctrl->generic.type) {
995           case CTRL_COLUMNS:
996             {
997                 static const int simplecols[1] = { 100 };
998                 columns_set_cols(cols, ctrl->columns.ncols,
999                                  (ctrl->columns.percentages ?
1000                                   ctrl->columns.percentages : simplecols));
1001             }
1002             continue;                  /* no actual control created */
1003           case CTRL_TABDELAY:
1004             {
1005                 struct uctrl *uc = dlg_find_byctrl(dp, ctrl->tabdelay.ctrl);
1006                 if (uc)
1007                     columns_taborder_last(cols, uc->toplevel);
1008             }
1009             continue;                  /* no actual control created */
1010         }
1011
1012         uc = smalloc(sizeof(struct uctrl));
1013         uc->ctrl = ctrl;
1014         uc->privdata = NULL;
1015         uc->privdata_needs_free = FALSE;
1016         uc->buttons = NULL;
1017         uc->entry = uc->list = uc->menu = NULL;
1018         uc->button = uc->optmenu = uc->text = NULL;
1019
1020         switch (ctrl->generic.type) {
1021           case CTRL_BUTTON:
1022             w = gtk_button_new_with_label(ctrl->generic.label);
1023             if (win) {
1024                 GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
1025                 if (ctrl->button.isdefault)
1026                     gtk_window_set_default(win, w);
1027             }
1028             gtk_signal_connect(GTK_OBJECT(w), "clicked",
1029                                GTK_SIGNAL_FUNC(button_clicked), dp);
1030             shortcut_add(scs, GTK_BIN(w)->child, ctrl->button.shortcut,
1031                          SHORTCUT_UCTRL, uc);
1032             break;
1033           case CTRL_CHECKBOX:
1034             w = gtk_check_button_new_with_label(ctrl->generic.label);
1035             gtk_signal_connect(GTK_OBJECT(w), "toggled",
1036                                GTK_SIGNAL_FUNC(button_toggled), dp);
1037             shortcut_add(scs, GTK_BIN(w)->child, ctrl->checkbox.shortcut,
1038                          SHORTCUT_UCTRL, uc);
1039             left = TRUE;
1040             break;
1041           case CTRL_RADIO:
1042             /*
1043              * Radio buttons get to go inside their own Columns, no
1044              * matter what.
1045              */
1046             {
1047                 gint i, *percentages;
1048                 GSList *group;
1049
1050                 w = columns_new(1);
1051                 if (ctrl->generic.label) {
1052                     GtkWidget *label = gtk_label_new(ctrl->generic.label);
1053                     columns_add(COLUMNS(w), label, 0, 1);
1054                     columns_force_left_align(COLUMNS(w), label);
1055                     gtk_widget_show(label);
1056                     shortcut_add(scs, label, ctrl->radio.shortcut,
1057                                  SHORTCUT_UCTRL, uc);
1058                 }
1059                 percentages = g_new(gint, ctrl->radio.ncolumns);
1060                 for (i = 0; i < ctrl->radio.ncolumns; i++) {
1061                     percentages[i] =
1062                         ((100 * (i+1) / ctrl->radio.ncolumns) -
1063                          100 * i / ctrl->radio.ncolumns);
1064                 }
1065                 columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns,
1066                                  percentages);
1067                 g_free(percentages);
1068                 group = NULL;
1069
1070                 uc->nbuttons = ctrl->radio.nbuttons;
1071                 uc->buttons = smalloc(uc->nbuttons * sizeof(GtkWidget *));
1072
1073                 for (i = 0; i < ctrl->radio.nbuttons; i++) {
1074                     GtkWidget *b;
1075                     gint colstart;
1076
1077                     b = (gtk_radio_button_new_with_label
1078                          (group, ctrl->radio.buttons[i]));
1079                     uc->buttons[i] = b;
1080                     group = gtk_radio_button_group(GTK_RADIO_BUTTON(b));
1081                     colstart = i % ctrl->radio.ncolumns;
1082                     columns_add(COLUMNS(w), b, colstart,
1083                                 (i == ctrl->radio.nbuttons-1 ?
1084                                  ctrl->radio.ncolumns - colstart : 1));
1085                     columns_force_left_align(COLUMNS(w), b);
1086                     gtk_widget_show(b);
1087                     gtk_signal_connect(GTK_OBJECT(b), "toggled",
1088                                        GTK_SIGNAL_FUNC(button_toggled), dp);
1089                     if (ctrl->radio.shortcuts) {
1090                         shortcut_add(scs, GTK_BIN(b)->child,
1091                                      ctrl->radio.shortcuts[i],
1092                                      SHORTCUT_UCTRL, uc);
1093                     }
1094                 }
1095             }
1096             break;
1097           case CTRL_EDITBOX:
1098             if (ctrl->editbox.has_list) {
1099                 w = gtk_combo_new();
1100                 gtk_combo_set_value_in_list(GTK_COMBO(w), FALSE, TRUE);
1101                 uc->entry = GTK_COMBO(w)->entry;
1102                 uc->list = GTK_COMBO(w)->list;
1103             } else {
1104                 w = gtk_entry_new();
1105                 if (ctrl->editbox.password)
1106                     gtk_entry_set_visibility(GTK_ENTRY(w), FALSE);
1107                 uc->entry = w;
1108             }
1109             gtk_signal_connect(GTK_OBJECT(uc->entry), "changed",
1110                                GTK_SIGNAL_FUNC(editbox_changed), dp);
1111             gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event",
1112                                GTK_SIGNAL_FUNC(editbox_key), dp);
1113             /*
1114              * Edit boxes, for some strange reason, have a minimum
1115              * width of 150 in GTK 1.2. We don't want this - we'd
1116              * rather the edit boxes acquired their natural width
1117              * from the column layout of the rest of the box.
1118              */
1119             {
1120                 GtkRequisition req;
1121                 gtk_widget_size_request(w, &req);
1122                 gtk_widget_set_usize(w, 10, req.height);
1123             }
1124             if (ctrl->generic.label) {
1125                 GtkWidget *label, *container;
1126
1127                 label = gtk_label_new(ctrl->generic.label);
1128                 shortcut_add(scs, label, ctrl->editbox.shortcut,
1129                              SHORTCUT_FOCUS, uc->entry);
1130
1131                 container = columns_new(4);
1132                 if (ctrl->editbox.percentwidth == 100) {
1133                     columns_add(COLUMNS(container), label, 0, 1);
1134                     columns_force_left_align(COLUMNS(container), label);
1135                     columns_add(COLUMNS(container), w, 0, 1);
1136                 } else {
1137                     gint percentages[2];
1138                     percentages[1] = ctrl->editbox.percentwidth;
1139                     percentages[0] = 100 - ctrl->editbox.percentwidth;
1140                     columns_set_cols(COLUMNS(container), 2, percentages);
1141                     columns_add(COLUMNS(container), label, 0, 1);
1142                     columns_force_left_align(COLUMNS(container), label);
1143                     columns_add(COLUMNS(container), w, 1, 1);
1144                 }
1145                 gtk_widget_show(label);
1146                 gtk_widget_show(w);
1147
1148                 w = container;
1149             }
1150             gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_out_event",
1151                                GTK_SIGNAL_FUNC(editbox_lostfocus), dp);
1152             break;
1153           case CTRL_FILESELECT:
1154           case CTRL_FONTSELECT:
1155             {
1156                 GtkWidget *ww;
1157                 GtkRequisition req;
1158                 char *browsebtn =
1159                     (ctrl->generic.type == CTRL_FILESELECT ?
1160                      "Browse..." : "Change...");
1161
1162                 gint percentages[] = { 75, 25 };
1163                 w = columns_new(4);
1164                 columns_set_cols(COLUMNS(w), 2, percentages);
1165
1166                 if (ctrl->generic.label) {
1167                     ww = gtk_label_new(ctrl->generic.label);
1168                     columns_add(COLUMNS(w), ww, 0, 2);
1169                     columns_force_left_align(COLUMNS(w), ww);
1170                     gtk_widget_show(ww);
1171                     shortcut_add(scs, ww,
1172                                  (ctrl->generic.type == CTRL_FILESELECT ?
1173                                   ctrl->fileselect.shortcut :
1174                                   ctrl->fontselect.shortcut),
1175                                  SHORTCUT_UCTRL, uc);
1176                 }
1177
1178                 uc->entry = ww = gtk_entry_new();
1179                 gtk_widget_size_request(ww, &req);
1180                 gtk_widget_set_usize(ww, 10, req.height);
1181                 columns_add(COLUMNS(w), ww, 0, 1);
1182                 gtk_widget_show(ww);
1183
1184                 uc->button = ww = gtk_button_new_with_label(browsebtn);
1185                 columns_add(COLUMNS(w), ww, 1, 1);
1186                 gtk_widget_show(ww);
1187
1188                 gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event",
1189                                    GTK_SIGNAL_FUNC(editbox_key), dp);
1190                 gtk_signal_connect(GTK_OBJECT(uc->entry), "changed",
1191                                    GTK_SIGNAL_FUNC(editbox_changed), dp);
1192                 gtk_signal_connect(GTK_OBJECT(ww), "clicked",
1193                                    GTK_SIGNAL_FUNC(filefont_clicked), dp);
1194             }
1195             break;
1196           case CTRL_LISTBOX:
1197             if (ctrl->listbox.height == 0) {
1198                 uc->optmenu = w = gtk_option_menu_new();
1199                 uc->menu = gtk_menu_new();
1200                 gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu);
1201                 gtk_object_set_data(GTK_OBJECT(uc->menu), "user-data",
1202                                     (gpointer)uc->optmenu);
1203             } else {
1204                 uc->list = gtk_list_new();
1205                 gtk_list_set_selection_mode(GTK_LIST(uc->list),
1206                                             (ctrl->listbox.multisel ?
1207                                              GTK_SELECTION_MULTIPLE :
1208                                              GTK_SELECTION_SINGLE));
1209                 w = gtk_scrolled_window_new(NULL, NULL);
1210                 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w),
1211                                                       uc->list);
1212                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
1213                                                GTK_POLICY_NEVER,
1214                                                GTK_POLICY_AUTOMATIC);
1215                 gtk_widget_show(uc->list);
1216                 gtk_signal_connect(GTK_OBJECT(uc->list), "selection-changed",
1217                                    GTK_SIGNAL_FUNC(list_selchange), dp);
1218
1219                 /*
1220                  * Adjust the height of the scrolled window to the
1221                  * minimum given by the height parameter.
1222                  * 
1223                  * This piece of guesswork is a horrid hack based
1224                  * on looking inside the GTK 1.2 sources
1225                  * (specifically gtkviewport.c, which appears to be
1226                  * the widget which provides the border around the
1227                  * scrolling area). Anyone lets me know how I can
1228                  * do this in a way which isn't at risk from GTK
1229                  * upgrades, I'd be grateful.
1230                  */
1231                 {
1232                     int edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
1233                     gtk_widget_set_usize(w, 10,
1234                                          2*edge + (ctrl->listbox.height *
1235                                                    listitemheight));
1236                 }
1237
1238                 if (ctrl->listbox.draglist) {
1239                     /*
1240                      * GTK doesn't appear to make it easy to
1241                      * implement a proper draggable list; so
1242                      * instead I'm just going to have to put an Up
1243                      * and a Down button to the right of the actual
1244                      * list box. Ah well.
1245                      */
1246                     GtkWidget *cols, *button;
1247                     static const gint percentages[2] = { 80, 20 };
1248
1249                     cols = columns_new(4);
1250                     columns_set_cols(COLUMNS(cols), 2, percentages);
1251                     columns_add(COLUMNS(cols), w, 0, 1);
1252                     gtk_widget_show(w);
1253                     button = gtk_button_new_with_label("Up");
1254                     columns_add(COLUMNS(cols), button, 1, 1);
1255                     gtk_widget_show(button);
1256                     gtk_signal_connect(GTK_OBJECT(button), "clicked",
1257                                        GTK_SIGNAL_FUNC(draglist_up), dp);
1258                     button = gtk_button_new_with_label("Down");
1259                     columns_add(COLUMNS(cols), button, 1, 1);
1260                     gtk_widget_show(button);
1261                     gtk_signal_connect(GTK_OBJECT(button), "clicked",
1262                                        GTK_SIGNAL_FUNC(draglist_down), dp);
1263
1264                     w = cols;
1265                 }
1266
1267             }
1268             if (ctrl->generic.label) {
1269                 GtkWidget *label, *container;
1270
1271                 label = gtk_label_new(ctrl->generic.label);
1272
1273                 container = columns_new(4);
1274                 if (ctrl->listbox.percentwidth == 100) {
1275                     columns_add(COLUMNS(container), label, 0, 1);
1276                     columns_force_left_align(COLUMNS(container), label);
1277                     columns_add(COLUMNS(container), w, 0, 1);
1278                 } else {
1279                     gint percentages[2];
1280                     percentages[1] = ctrl->listbox.percentwidth;
1281                     percentages[0] = 100 - ctrl->listbox.percentwidth;
1282                     columns_set_cols(COLUMNS(container), 2, percentages);
1283                     columns_add(COLUMNS(container), label, 0, 1);
1284                     columns_force_left_align(COLUMNS(container), label);
1285                     columns_add(COLUMNS(container), w, 1, 1);
1286                 }
1287                 gtk_widget_show(label);
1288                 gtk_widget_show(w);
1289                 shortcut_add(scs, label, ctrl->listbox.shortcut,
1290                              SHORTCUT_UCTRL, uc);
1291                 w = container;
1292             }
1293             break;
1294           case CTRL_TEXT:
1295             uc->text = w = gtk_label_new(ctrl->generic.label);
1296             gtk_label_set_line_wrap(GTK_LABEL(w), TRUE);
1297             /* FIXME: deal with wrapping! */
1298             break;
1299         }
1300
1301         assert(w != NULL);
1302
1303         columns_add(cols, w,
1304                     COLUMN_START(ctrl->generic.column),
1305                     COLUMN_SPAN(ctrl->generic.column));
1306         if (left)
1307             columns_force_left_align(cols, w);
1308         gtk_widget_show(w);
1309
1310         uc->toplevel = w;
1311         dlg_add_uctrl(dp, uc);
1312     }
1313
1314     return ret;
1315 }
1316
1317 struct selparam {
1318     struct dlgparam *dp;
1319     Panels *panels;
1320     GtkWidget *panel, *treeitem;
1321     struct Shortcuts shortcuts;
1322 };
1323
1324 static void treeitem_sel(GtkItem *item, gpointer data)
1325 {
1326     struct selparam *sp = (struct selparam *)data;
1327
1328     panels_switch_to(sp->panels, sp->panel);
1329
1330     sp->dp->shortcuts = &sp->shortcuts;
1331 }
1332
1333 void destroy(GtkWidget *widget, gpointer data)
1334 {
1335     gtk_main_quit();
1336 }
1337
1338 int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
1339 {
1340     struct dlgparam *dp = (struct dlgparam *)data;
1341
1342     if (event->keyval == GDK_Escape) {
1343         gtk_main_quit();
1344         return TRUE;
1345     }
1346
1347     if ((event->state & GDK_MOD1_MASK) &&
1348         (unsigned char)event->string[0] > 0 &&
1349         (unsigned char)event->string[0] <= 127) {
1350         int schr = (unsigned char)event->string[0];
1351         struct Shortcut *sc = &dp->shortcuts->sc[schr];
1352
1353         switch (sc->action) {
1354           case SHORTCUT_FOCUS:
1355             gtk_widget_grab_focus(sc->widget);
1356             break;
1357           case SHORTCUT_UCTRL:
1358             /*
1359              * We must do something sensible with a uctrl.
1360              * Precisely what this is depends on the type of
1361              * control.
1362              */
1363             switch (sc->uc->ctrl->generic.type) {
1364               case CTRL_CHECKBOX:
1365               case CTRL_BUTTON:
1366                 /* Check boxes and buttons get the focus _and_ get toggled. */
1367                 gtk_widget_grab_focus(sc->uc->toplevel);
1368                 gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->toplevel),
1369                                         "clicked");
1370                 break;
1371               case CTRL_FILESELECT:
1372               case CTRL_FONTSELECT:
1373                 /* File/font selectors have their buttons pressed (ooer),
1374                  * and focus transferred to the edit box. */
1375                 gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->button),
1376                                         "clicked");
1377                 gtk_widget_grab_focus(sc->uc->entry);
1378                 break;
1379               case CTRL_RADIO:
1380                 /*
1381                  * Radio buttons are fun, because they have
1382                  * multiple shortcuts. We must find whether the
1383                  * activated shortcut is the shortcut for the whole
1384                  * group, or for a particular button. In the former
1385                  * case, we find the currently selected button and
1386                  * focus it; in the latter, we focus-and-click the
1387                  * button whose shortcut was pressed.
1388                  */
1389                 if (schr == sc->uc->ctrl->radio.shortcut) {
1390                     int i;
1391                     for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
1392                         if (gtk_toggle_button_get_active
1393                             (GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) {
1394                             gtk_widget_grab_focus(sc->uc->buttons[i]);
1395                         }
1396                 } else if (sc->uc->ctrl->radio.shortcuts) {
1397                     int i;
1398                     for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
1399                         if (schr == sc->uc->ctrl->radio.shortcuts[i]) {
1400                             gtk_widget_grab_focus(sc->uc->buttons[i]);
1401                             gtk_signal_emit_by_name
1402                                 (GTK_OBJECT(sc->uc->buttons[i]), "clicked");
1403                         }
1404                 }
1405                 break;
1406               case CTRL_LISTBOX:
1407                 /*
1408                  * List boxes are fun too. If the list is really an
1409                  * option menu, we simply focus and click it.
1410                  * Otherwise we must do something clever (FIXME).
1411                  */
1412                 if (sc->uc->optmenu) {
1413                     GdkEventButton bev;
1414                     gint returnval;
1415
1416                     gtk_widget_grab_focus(sc->uc->optmenu);
1417                     /* Option menus don't work using the "clicked" signal.
1418                      * We need to manufacture a button press event :-/ */
1419                     bev.type = GDK_BUTTON_PRESS;
1420                     bev.button = 1;
1421                     gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->optmenu),
1422                                             "button_press_event",
1423                                             &bev, &returnval);
1424                 } else {
1425                 }
1426                 break;
1427             }
1428             break;
1429         }
1430     }
1431
1432     return FALSE;
1433 }
1434
1435 void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
1436                   int chr, int action, void *ptr)
1437 {
1438     GtkLabel *label = GTK_LABEL(labelw);
1439     gchar *currstr, *pattern;
1440     int i;
1441
1442     if (chr == NO_SHORTCUT)
1443         return;
1444
1445     chr = tolower((unsigned char)chr);
1446
1447     assert(scs->sc[chr].action == SHORTCUT_EMPTY);
1448
1449     scs->sc[chr].action = action;
1450
1451     if (action == SHORTCUT_FOCUS) {
1452         scs->sc[chr].uc = NULL;
1453         scs->sc[chr].widget = (GtkWidget *)ptr;
1454     } else {
1455         scs->sc[chr].widget = NULL;
1456         scs->sc[chr].uc = (struct uctrl *)ptr;
1457     }
1458
1459     gtk_label_get(label, &currstr);
1460     for (i = 0; currstr[i]; i++)
1461         if (tolower((unsigned char)currstr[i]) == chr) {
1462             GtkRequisition req;
1463
1464             pattern = dupprintf("%*s_", i, "");
1465
1466             gtk_widget_size_request(GTK_WIDGET(label), &req);
1467             gtk_label_set_pattern(label, pattern);
1468             gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height);
1469
1470             sfree(pattern);
1471             break;
1472         }
1473 }
1474
1475 void do_config_box(void)
1476 {
1477     GtkWidget *window, *hbox, *vbox, *cols, *label,
1478         *tree, *treescroll, *panels, *panelvbox;
1479     int index, level, listitemheight;
1480     struct controlbox *ctrlbox;
1481     char *path;
1482     GtkTreeItem *treeitemlevels[8];
1483     GtkTree *treelevels[8];
1484     Config cfg;
1485     struct dlgparam dp;
1486     struct sesslist sl;
1487     struct Shortcuts scs;
1488
1489     struct selparam *selparams = NULL;
1490     int nselparams = 0, selparamsize = 0;
1491
1492     do_defaults(NULL, &cfg);
1493
1494     dlg_init(&dp);
1495
1496     {
1497         GtkWidget *listitem = gtk_list_item_new_with_label("foo");
1498         GtkRequisition req;
1499         gtk_widget_size_request(listitem, &req);
1500         listitemheight = req.height;
1501         gtk_widget_unref(listitem);
1502     }
1503
1504     sl.nsessions = 0;
1505
1506     for (index = 0; index < lenof(scs.sc); index++) {
1507         scs.sc[index].action = SHORTCUT_EMPTY;
1508     }
1509
1510     ctrlbox = ctrl_new_box();
1511     setup_config_box(ctrlbox, &sl, FALSE, 0);
1512     unix_setup_config_box(ctrlbox, FALSE);
1513
1514     window = gtk_dialog_new();
1515     hbox = gtk_hbox_new(FALSE, 4);
1516     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), hbox, TRUE, TRUE, 0);
1517     gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
1518     gtk_widget_show(hbox);
1519     vbox = gtk_vbox_new(FALSE, 4);
1520     gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
1521     gtk_widget_show(vbox);
1522     cols = columns_new(4);
1523     gtk_box_pack_start(GTK_BOX(vbox), cols, FALSE, FALSE, 0);
1524     gtk_widget_show(cols);
1525     label = gtk_label_new("Category:");
1526     columns_add(COLUMNS(cols), label, 0, 1);
1527     columns_force_left_align(COLUMNS(cols), label);
1528     gtk_widget_show(label);
1529     treescroll = gtk_scrolled_window_new(NULL, NULL);
1530     tree = gtk_tree_new();
1531     /* FIXME: focusing treescroll doesn't help */
1532     shortcut_add(&scs, label, 'g', SHORTCUT_FOCUS, tree);
1533     gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
1534     gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
1535     gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
1536                                           tree);
1537     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
1538                                    GTK_POLICY_NEVER,
1539                                    GTK_POLICY_AUTOMATIC);
1540     gtk_widget_show(tree);
1541     gtk_widget_show(treescroll);
1542     gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0);
1543     panels = panels_new();
1544     gtk_box_pack_start(GTK_BOX(hbox), panels, TRUE, TRUE, 0);
1545     gtk_widget_show(panels);
1546
1547     panelvbox = NULL;
1548     path = NULL;
1549     level = 0;
1550     for (index = 0; index < ctrlbox->nctrlsets; index++) {
1551         struct controlset *s = ctrlbox->ctrlsets[index];
1552         GtkWidget *w;
1553
1554         if (!*s->pathname) {
1555             w = layout_ctrls(&dp, &scs, s, listitemheight, GTK_WINDOW(window));
1556             gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->action_area),
1557                                w, TRUE, TRUE, 0);
1558         } else {
1559             int j = path ? ctrl_path_compare(s->pathname, path) : 0;
1560             if (j != INT_MAX) {        /* add to treeview, start new panel */
1561                 char *c;
1562                 GtkWidget *treeitem;
1563                 int first;
1564
1565                 /*
1566                  * We expect never to find an implicit path
1567                  * component. For example, we expect never to see
1568                  * A/B/C followed by A/D/E, because that would
1569                  * _implicitly_ create A/D. All our path prefixes
1570                  * are expected to contain actual controls and be
1571                  * selectable in the treeview; so we would expect
1572                  * to see A/D _explicitly_ before encountering
1573                  * A/D/E.
1574                  */
1575                 assert(j == ctrl_path_elements(s->pathname) - 1);
1576
1577                 c = strrchr(s->pathname, '/');
1578                 if (!c)
1579                     c = s->pathname;
1580                 else
1581                     c++;
1582
1583                 treeitem = gtk_tree_item_new_with_label(c);
1584                 assert(j-1 < level);
1585                 if (j > 0) {
1586                     if (!treelevels[j-1]) {
1587                         treelevels[j-1] = GTK_TREE(gtk_tree_new());
1588                         gtk_tree_item_set_subtree
1589                             (treeitemlevels[j-1],
1590                              GTK_WIDGET(treelevels[j-1]));
1591                         gtk_tree_item_expand(treeitemlevels[j-1]);
1592                     }
1593                     gtk_tree_append(treelevels[j-1], treeitem);
1594                 } else {
1595                     gtk_tree_append(GTK_TREE(tree), treeitem);
1596                 }
1597                 treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
1598                 treelevels[j] = NULL;
1599                 level = j+1;
1600
1601                 gtk_widget_show(treeitem);
1602
1603                 path = s->pathname;
1604
1605                 first = (panelvbox == NULL);
1606
1607                 panelvbox = gtk_vbox_new(FALSE, 4);
1608                 gtk_container_add(GTK_CONTAINER(panels), panelvbox);
1609                 if (first) {
1610                     panels_switch_to(PANELS(panels), panelvbox);
1611                     gtk_tree_select_child(GTK_TREE(tree), treeitem);
1612                 }
1613
1614                 if (nselparams >= selparamsize) {
1615                     selparamsize += 16;
1616                     selparams = srealloc(selparams,
1617                                          selparamsize * sizeof(*selparams));
1618                 }
1619                 selparams[nselparams].dp = &dp;
1620                 selparams[nselparams].panels = PANELS(panels);
1621                 selparams[nselparams].panel = panelvbox;
1622                 selparams[nselparams].shortcuts = scs;   /* structure copy */
1623                 selparams[nselparams].treeitem = treeitem;
1624                 nselparams++;
1625
1626             }
1627
1628             w = layout_ctrls(&dp,
1629                              &selparams[nselparams-1].shortcuts,
1630                              s, listitemheight, NULL);
1631             gtk_box_pack_start(GTK_BOX(panelvbox), w, FALSE, FALSE, 0);
1632             gtk_widget_show(w);
1633         }
1634     }
1635
1636     for (index = 0; index < nselparams; index++) {
1637         gtk_signal_connect(GTK_OBJECT(selparams[index].treeitem), "select",
1638                            GTK_SIGNAL_FUNC(treeitem_sel),
1639                            &selparams[index]);
1640     }
1641
1642     dp.data = &cfg;
1643     dlg_refresh(NULL, &dp);
1644
1645     dp.shortcuts = &selparams[0].shortcuts;
1646
1647     gtk_widget_show(window);
1648
1649     gtk_signal_connect(GTK_OBJECT(window), "destroy",
1650                        GTK_SIGNAL_FUNC(destroy), NULL);
1651     gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
1652                        GTK_SIGNAL_FUNC(win_key_press), &dp);
1653
1654     gtk_main();
1655
1656     dlg_cleanup(&dp);
1657     sfree(selparams);
1658 }
1659
1660 /* ======================================================================
1661  * Below here is a stub main program which allows the dialog box
1662  * code to be compiled and tested with a minimal amount of the rest
1663  * of PuTTY.
1664  */
1665
1666 #ifdef TESTMODE
1667
1668 /* Compile command for testing:
1669
1670 gcc -g -o gtkdlg gtk{dlg,cols,panel}.c ../{config,dialog,settings}.c \
1671                  ../{misc,tree234,be_none}.c ux{store,misc,print,cfg}.c \
1672                  -I. -I.. -I../charset -DTESTMODE `gtk-config --cflags --libs`
1673  */
1674
1675 void modalfatalbox(char *p, ...)
1676 {
1677     va_list ap;
1678     fprintf(stderr, "FATAL ERROR: ");
1679     va_start(ap, p);
1680     vfprintf(stderr, p, ap);
1681     va_end(ap);
1682     fputc('\n', stderr);
1683     exit(1);
1684 }
1685
1686 char *cp_name(int codepage)
1687 {
1688     return (codepage == 123 ? "testing123" :
1689             codepage == 234 ? "testing234" :
1690             codepage == 345 ? "testing345" :
1691             "unknown");
1692 }
1693
1694 char *cp_enumerate(int index)
1695 {
1696     return (index == 0 ? "testing123" :
1697             index == 1 ? "testing234" :
1698             NULL);
1699 }
1700
1701 int decode_codepage(char *cp_name)
1702 {
1703     return (!strcmp(cp_name, "testing123") ? 123 :
1704             !strcmp(cp_name, "testing234") ? 234 :
1705             !strcmp(cp_name, "testing345") ? 345 :
1706             -2);
1707 }
1708
1709 struct printer_enum_tag { int dummy; } printer_test;
1710
1711 printer_enum *printer_start_enum(int *nprinters_ptr) {
1712     *nprinters_ptr = 2;
1713     return &printer_test;
1714 }
1715 char *printer_get_name(printer_enum *pe, int i) {
1716     return (i==0 ? "lpr" : i==1 ? "lpr -Pfoobar" : NULL);
1717 }
1718 void printer_finish_enum(printer_enum *pe) { }
1719
1720 char *platform_default_s(const char *name)
1721 {
1722     return NULL;
1723 }
1724
1725 int platform_default_i(const char *name, int def)
1726 {
1727     return def;
1728 }
1729
1730 FontSpec platform_default_fontspec(const char *name)
1731 {
1732     FontSpec ret;
1733     if (!strcmp(name, "Font"))
1734         strcpy(ret.name, "fixed");
1735     else
1736         *ret.name = '\0';
1737     return ret;
1738 }
1739
1740 Filename platform_default_filename(const char *name)
1741 {
1742     Filename ret;
1743     if (!strcmp(name, "LogFileName"))
1744         strcpy(ret.path, "putty.log");
1745     else
1746         *ret.path = '\0';
1747     return ret;
1748 }
1749
1750 char *x_get_default(const char *key)
1751 {
1752     return NULL;
1753 }
1754
1755 int main(int argc, char **argv)
1756 {
1757     gtk_init(&argc, &argv);
1758     do_config_box();
1759     return 0;
1760 }
1761
1762 #endif