OSDN Git Service

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