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