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