From dc11417aeeec0735071cf98347af6f9616ba6a2e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 31 Aug 2015 15:45:18 +0100 Subject: [PATCH] Stop using GtkDialog (for most purposes) in GTK 3! They've now deprecated gtk_dialog_get_action_area, because they really want a dialog box's action area to be filled with nothing but buttons controlled by GTK which end the dialog with a response code. But we're accustomed to putting all sorts of other things in our action area - non-buttons, buttons that don't end the dialog, and sub-widgets that do layout - and so I think it's no longer sensible to be trying to coerce our use cases into GtkDialog. Hence, I'm introducing a set of wrapper functions which equivocate between a GtkDialog for GTK1 and GTK2, and a GtkWindow with a vbox in it for GTK3, and I'll lay out the action area by hand. (Not everything has sensible layout and margins in the new GTK3 system yet, but I can sort that out later.) Because the new functions are needed by gtkask.c, which doesn't link against gtkdlg.c or include putty.h, I've put them in a new source file and header file pair gtkmisc.[ch] which is common to gtkask and the main GTK edifice. --- Recipe | 4 +- unix/gtkask.c | 18 +++--- unix/gtkdlg.c | 127 +++++++------------------------------------ unix/gtkmisc.c | 145 +++++++++++++++++++++++++++++++++++++++++++++++++ unix/gtkmisc.h | 15 +++++ 5 files changed, 193 insertions(+), 116 deletions(-) create mode 100644 unix/gtkmisc.c create mode 100644 unix/gtkmisc.h diff --git a/Recipe b/Recipe index 6552fc61..89609df6 100644 --- a/Recipe +++ b/Recipe @@ -207,7 +207,7 @@ GUITERM = TERMINAL window windlg winctrls sizetip winucs winprint # Same thing on Unix. UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing callback miscucs -GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols xkeysym +GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym OSXTERM = UXTERM osxwin osxdlg osxctrls # Non-SSH back ends (putty, puttytel, plink). @@ -306,7 +306,7 @@ psftp : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC pageant : [X] uxpgnt uxagentc pageant sshrsa sshpubk sshdes sshbn sshmd5 + version tree234 misc sshaes sshsha sshdss sshsh256 sshsh512 sshecc + conf uxsignal nocproxy nogss be_none x11fwd ux_x11 uxcons gtkask - + UXMISC + + gtkmisc UXMISC PuTTY : [MX] osxmain OSXTERM OSXMISC CHARSET U_BE_ALL NONSSH UXSSH + ux_x11 uxpty uxsignal testback putty.icns info.plist diff --git a/unix/gtkask.c b/unix/gtkask.c index b58cc42b..8d984a35 100644 --- a/unix/gtkask.c +++ b/unix/gtkask.c @@ -16,6 +16,7 @@ #include "gtkfont.h" #include "gtkcompat.h" +#include "gtkmisc.h" #include "misc.h" @@ -289,6 +290,7 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, const char *prompt_text) { int i; + GtkBox *action_area; ctx->passlen = 0; ctx->passsize = 2048; @@ -297,13 +299,13 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, /* * Create widgets. */ - ctx->dialog = gtk_dialog_new(); + ctx->dialog = our_dialog_new(); gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title); + gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER); ctx->promptlabel = gtk_label_new(prompt_text); gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), TRUE); - gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area - (GTK_DIALOG(ctx->dialog))), - ctx->promptlabel); + our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog), + ctx->promptlabel, TRUE, TRUE, 0); #if GTK_CHECK_VERSION(2,0,0) ctx->imc = gtk_im_multicontext_new(); #endif @@ -319,6 +321,9 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, return "unable to allocate colours"; } #endif + + action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog)); + for (i = 0; i < N_DRAWING_AREAS; i++) { ctx->drawingareas[i].area = gtk_drawing_area_new(); #ifndef DRAW_DEFAULT_CAIRO @@ -330,9 +335,8 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, * context-sensitive way, like measuring the size of some * piece of template text. */ gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32); - gtk_container_add(GTK_CONTAINER(gtk_dialog_get_action_area - (GTK_DIALOG(ctx->dialog))), - ctx->drawingareas[i].area); + gtk_box_pack_end(action_area, ctx->drawingareas[i].area, + TRUE, TRUE, 5); g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), "configure_event", G_CALLBACK(configure_area), diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 6c608ab7..47a8fae1 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -12,10 +12,13 @@ #include #endif +#define MAY_REFER_TO_GTK_IN_HEADERS + #include "putty.h" #include "gtkcompat.h" #include "gtkcols.h" #include "gtkfont.h" +#include "gtkmisc.h" #ifndef NOT_X_WINDOWS #include @@ -2930,84 +2933,6 @@ int get_listitemheight(GtkWidget *w) #endif } -void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w) -{ -#if !GTK_CHECK_VERSION(2,0,0) - - /* - * In GTK 1, laying out the buttons at the bottom of the - * configuration box is nice and easy, because a GtkDialog's - * action_area is a GtkHBox which stretches to cover the full - * width of the dialog. So we just put our Columns widget - * straight into that hbox, and it ends up just where we want - * it. - */ - gtk_box_pack_start(GTK_BOX(dlg->action_area), w, TRUE, TRUE, 0); - -#else - /* - * In GTK 2, the action area is now a GtkHButtonBox and its - * layout behaviour seems to be different: it doesn't stretch - * to cover the full width of the window, but instead finds its - * own preferred width and right-aligns that within the window. - * This isn't what we want, because we have both left-aligned - * and right-aligned buttons coming out of the above call to - * layout_ctrls(), and right-aligning the whole thing will - * result in the former being centred and looking weird. - * - * So instead we abandon the dialog's action area completely: - * we gtk_widget_hide() it in the below code, and we also call - * gtk_dialog_set_has_separator() to remove the separator above - * it. We then insert our own action area into the end of the - * dialog's main vbox, and add our own separator above that. - * - * (Ideally, if we were a native GTK app, we would use the - * GtkHButtonBox's _own_ innate ability to support one set of - * buttons being right-aligned and one left-aligned. But to do - * that here, we would have to either (a) pick apart our cross- - * platform layout structures and treat them specially for this - * particular set of controls, which would be painful, or else - * (b) develop a special and simpler cross-platform - * representation for these particular controls, and introduce - * special-case code into all the _other_ platforms to handle - * it. Neither appeals. Therefore, I regretfully discard the - * GTKHButtonBox and go it alone.) - */ - -#if GTK_CHECK_VERSION(3,0,0) - gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(dlg)), - w, FALSE, TRUE, 0); -#else - GtkWidget *align; - align = gtk_alignment_new(0, 0, 1, 1); - gtk_container_add(GTK_CONTAINER(align), w); - /* - * The purpose of this GtkAlignment is to provide padding - * around the buttons. The padding we use is twice the padding - * used in our GtkColumns, because we nest two GtkColumns most - * of the time (one separating the tree view from the main - * controls, and another for the main controls themselves). - */ -#if GTK_CHECK_VERSION(2,4,0) - gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8); -#endif - gtk_widget_show(align); - gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(dlg)), - align, FALSE, TRUE, 0); -#endif - - w = gtk_hseparator_new(); - gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(dlg)), - w, FALSE, TRUE, 0); - gtk_widget_show(w); - gtk_widget_hide(gtk_dialog_get_action_area(dlg)); -#if !GTK_CHECK_VERSION(3,0,0) - /* This cosmetic property is withdrawn in GTK 3's GtkDialog */ - g_object_set(G_OBJECT(dlg), "has-separator", TRUE, (const char *)NULL); -#endif -#endif -} - #if GTK_CHECK_VERSION(2,0,0) void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree) { @@ -3064,7 +2989,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, scs.sc[index].action = SHORTCUT_EMPTY; } - window = gtk_dialog_new(); + window = our_dialog_new(); ctrlbox = ctrl_new_box(); protocol = conf_get_int(conf, CONF_protocol); @@ -3074,9 +2999,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, gtk_window_set_title(GTK_WINDOW(window), title); hbox = gtk_hbox_new(FALSE, 4); - gtk_box_pack_start - (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(window))), - hbox, TRUE, TRUE, 0); + our_dialog_add_to_content_area(GTK_WINDOW(window), hbox, TRUE, TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(hbox), 10); gtk_widget_show(hbox); vbox = gtk_vbox_new(FALSE, 4); @@ -3130,7 +3053,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, if (!*s->pathname) { w = layout_ctrls(&dp, &scs, s, GTK_WINDOW(window)); - set_dialog_action_area(GTK_DIALOG(window), w); + our_dialog_set_action_area(GTK_WINDOW(window), w); } else { int j = path ? ctrl_path_compare(s->pathname, path) : 0; if (j != INT_MAX) { /* add to treeview, start new panel */ @@ -3451,17 +3374,15 @@ int messagebox(GtkWidget *parentwin, const char *title, const char *msg, s1 = ctrl_getset(ctrlbox, "x", "", ""); ctrl_text(s1, msg, HELPCTX(no_help)); - window = gtk_dialog_new(); + window = our_dialog_new(); gtk_window_set_title(GTK_WINDOW(window), title); w0 = layout_ctrls(&dp, &scs, s0, GTK_WINDOW(window)); - set_dialog_action_area(GTK_DIALOG(window), w0); + our_dialog_set_action_area(GTK_WINDOW(window), w0); gtk_widget_show(w0); w1 = layout_ctrls(&dp, &scs, s1, GTK_WINDOW(window)); gtk_container_set_border_width(GTK_CONTAINER(w1), 10); gtk_widget_set_size_request(w1, minwid+20, -1); - gtk_box_pack_start - (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(window))), - w1, TRUE, TRUE, 0); + our_dialog_add_to_content_area(GTK_WINDOW(window), w1, TRUE, TRUE, 0); gtk_widget_show(w1); dp.shortcuts = &scs; @@ -3701,6 +3622,7 @@ static void licence_clicked(GtkButton *button, gpointer data) void about_box(void *window) { GtkWidget *w; + GtkBox *action_area; char *title; if (aboutbox) { @@ -3708,7 +3630,7 @@ void about_box(void *window) return; } - aboutbox = gtk_dialog_new(); + aboutbox = our_dialog_new(); gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10); title = dupcat("About ", appname, NULL); gtk_window_set_title(GTK_WINDOW(aboutbox), title); @@ -3717,36 +3639,29 @@ void about_box(void *window) w = gtk_button_new_with_label("Close"); gtk_widget_set_can_default(w, TRUE); gtk_window_set_default(GTK_WINDOW(aboutbox), w); - gtk_box_pack_end(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(aboutbox))), - w, FALSE, FALSE, 0); + action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox)); + gtk_box_pack_end(action_area, w, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(w), "clicked", G_CALLBACK(about_close_clicked), NULL); gtk_widget_show(w); w = gtk_button_new_with_label("View Licence"); gtk_widget_set_can_default(w, TRUE); - gtk_box_pack_end(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(aboutbox))), - w, FALSE, FALSE, 0); + gtk_box_pack_end(action_area, w, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(w), "clicked", G_CALLBACK(licence_clicked), NULL); gtk_widget_show(w); w = gtk_label_new(appname); - gtk_box_pack_start - (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(aboutbox))), - w, FALSE, FALSE, 0); + our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, FALSE, FALSE, 0); gtk_widget_show(w); w = gtk_label_new(ver); - gtk_box_pack_start - (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(aboutbox))), - w, FALSE, FALSE, 5); + our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, FALSE, FALSE, 5); gtk_widget_show(w); w = gtk_label_new("Copyright 1997-2015 Simon Tatham. All rights reserved"); - gtk_box_pack_start - (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(aboutbox))), - w, FALSE, FALSE, 5); + our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, FALSE, FALSE, 5); gtk_widget_show(w); set_transient_window_pos(GTK_WIDGET(window), aboutbox); @@ -3922,12 +3837,12 @@ void showeventlog(void *estuff, void *parentwin) c->listbox.percentages[1] = 10; c->listbox.percentages[2] = 65; - es->window = window = gtk_dialog_new(); + es->window = window = our_dialog_new(); title = dupcat(appname, " Event Log", NULL); gtk_window_set_title(GTK_WINDOW(window), title); sfree(title); w0 = layout_ctrls(&es->dp, &es->scs, s0, GTK_WINDOW(window)); - set_dialog_action_area(GTK_DIALOG(window), w0); + our_dialog_set_action_area(GTK_WINDOW(window), w0); gtk_widget_show(w0); w1 = layout_ctrls(&es->dp, &es->scs, s1, GTK_WINDOW(window)); gtk_container_set_border_width(GTK_CONTAINER(w1), 10); @@ -3935,9 +3850,7 @@ void showeventlog(void *estuff, void *parentwin) ("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS " "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"), -1); - gtk_box_pack_start - (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(window))), - w1, TRUE, TRUE, 0); + our_dialog_add_to_content_area(GTK_WINDOW(window), w1, TRUE, TRUE, 0); gtk_widget_show(w1); es->dp.data = es; diff --git a/unix/gtkmisc.c b/unix/gtkmisc.c new file mode 100644 index 00000000..d6cfccc2 --- /dev/null +++ b/unix/gtkmisc.c @@ -0,0 +1,145 @@ +/* + * Miscellaneous GTK helper functions. + */ + +#include +#include +#include +#include + +#include +#if !GTK_CHECK_VERSION(3,0,0) +#include +#endif + +#include "putty.h" +#include "gtkcompat.h" + +/* ---------------------------------------------------------------------- + * Functions to arrange controls in a basically dialog-like window. + * + * The best method for doing this has varied wildly with versions of + * GTK, hence the set of wrapper functions here. + * + * In GTK 1, a GtkDialog has an 'action_area' at the bottom, which is + * a GtkHBox which stretches to cover the full width of the dialog. So + * we can either add buttons or other widgets to that box directly, or + * alternatively we can fill the hbox with some layout class of our + * own such as a Columns widget. + * + * In GTK 2, the action area has become a GtkHButtonBox, and its + * layout behaviour seems to be different and not what we want. So + * instead we abandon the dialog's action area completely: we + * gtk_widget_hide() it in the below code, and we also call + * gtk_dialog_set_has_separator() to remove the separator above it. We + * then insert our own action area into the end of the dialog's main + * vbox, and add our own separator above that. + * + * In GTK 3, we typically don't even want to use GtkDialog at all, + * because GTK 3 has become a lot more restrictive about what you can + * sensibly use GtkDialog for - it deprecates direct access to the + * action area in favour of making you provide nothing but + * dialog-ending buttons in the form of (text, response code) pairs, + * so you can't put any other kind of control in there, or fiddle with + * alignment and positioning, or even have a button that _doesn't_ end + * the dialog (e.g. 'View Licence' in our About box). So instead of + * GtkDialog, we use a straight-up GtkWindow and have it contain a + * vbox as its (unique) child widget; and we implement the action area + * by adding a separator and another widget at the bottom of that + * vbox. + */ + +GtkWidget *our_dialog_new(void) +{ +#if GTK_CHECK_VERSION(3,0,0) + /* + * See comment in our_dialog_set_action_area(): in GTK 3, we use + * GtkWindow in place of GtkDialog for most purposes. + */ + GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL); + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); + gtk_container_add(GTK_CONTAINER(w), vbox); + gtk_widget_show(vbox); + return w; +#else + return gtk_dialog_new(); +#endif +} + +void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w) +{ +#if !GTK_CHECK_VERSION(2,0,0) + + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), + w, TRUE, TRUE, 0); + +#elif !GTK_CHECK_VERSION(3,0,0) + + GtkWidget *align; + align = gtk_alignment_new(0, 0, 1, 1); + gtk_container_add(GTK_CONTAINER(align), w); + /* + * The purpose of this GtkAlignment is to provide padding + * around the buttons. The padding we use is twice the padding + * used in our GtkColumns, because we nest two GtkColumns most + * of the time (one separating the tree view from the main + * controls, and another for the main controls themselves). + */ +#if GTK_CHECK_VERSION(2,4,0) + gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8); +#endif + gtk_widget_show(align); + gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), + align, FALSE, TRUE, 0); + + w = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), + w, FALSE, TRUE, 0); + gtk_widget_show(w); + gtk_widget_hide(gtk_dialog_get_action_area(GTK_DIALOG(dlg))); + g_object_set(G_OBJECT(dlg), "has-separator", TRUE, (const char *)NULL); + +#else /* GTK 3 */ + + /* GtkWindow is a GtkBin, hence contains exactly one child, which + * here we always expect to be a vbox */ + GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg))); + + GtkWidget *sep = gtk_hseparator_new(); + gtk_box_pack_end(vbox, sep, FALSE, TRUE, 0); + gtk_widget_show(sep); + + g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); + gtk_box_pack_end(vbox, w, FALSE, TRUE, 0); + +#endif +} + +GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg) +{ +#if GTK_CHECK_VERSION(3,0,0) + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + our_dialog_set_action_area(dlg, hbox); + gtk_widget_show(hbox); + return GTK_BOX(hbox); +#else /* not GTK 3 */ + return GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(dlg))); +#endif +} + +void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w, + gboolean expand, gboolean fill, + guint padding) +{ +#if GTK_CHECK_VERSION(3,0,0) + /* GtkWindow is a GtkBin, hence contains exactly one child, which + * here we always expect to be a vbox */ + GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg))); + + gtk_box_pack_start(vbox, w, expand, fill, padding); +#else + gtk_box_pack_start + (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), + w, expand, fill, padding); +#endif +} diff --git a/unix/gtkmisc.h b/unix/gtkmisc.h new file mode 100644 index 00000000..08daa85b --- /dev/null +++ b/unix/gtkmisc.h @@ -0,0 +1,15 @@ +/* + * Miscellaneous helper functions for GTK. + */ + +#ifndef PUTTY_GTKMISC_H +#define PUTTY_GTKMISC_H + +GtkWidget *our_dialog_new(void); +void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w, + gboolean expand, gboolean fill, + guint padding); +void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w); +GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg); + +#endif /* PUTTY_GTKMISC_H */ -- 2.45.2