#include <gdk/gdkkeysyms.h>
#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 <gdk/gdkx.h>
#endif
}
-void align_label_left(GtkLabel *label)
-{
-#if GTK_CHECK_VERSION(3,16,0)
- gtk_label_set_xalign(label, 0.0);
-#else
- gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-#endif
-}
-
void dlg_error_msg(void *dlg, const char *msg)
{
struct dlgparam *dp = (struct dlgparam *)dlg;
G_TYPE_STRING);
w = gtk_combo_box_new_with_model_and_entry
(GTK_TREE_MODEL(uc->listmodel));
+ g_object_set(G_OBJECT(w), "entry-text-column", 1,
+ (const char *)NULL);
/* We cannot support password combo boxes. */
assert(!ctrl->editbox.password);
uc->combo = w;
*/
g_signal_connect(G_OBJECT(w), "changed",
G_CALLBACK(droplist_selchange), dp);
+
+ g_signal_connect(G_OBJECT(w), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
#endif
} else {
#if !GTK_CHECK_VERSION(2,0,0)
uc->treeview = w;
g_signal_connect(G_OBJECT(w), "row-activated",
G_CALLBACK(listbox_doubleclick), dp);
+ g_signal_connect(G_OBJECT(w), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
g_signal_connect(G_OBJECT(sel), "changed",
G_CALLBACK(listbox_selchange), dp);
g_object_set(G_OBJECT(cellrend),
"ellipsize", PANGO_ELLIPSIZE_END,
"ellipsize-set", TRUE,
- NULL);
+ (const char *)NULL);
}
column = gtk_tree_view_column_new_with_attributes
("heading", cellrend, "text", i+1, (char *)NULL);
#endif
shortcut_add(scs, label, ctrl->listbox.shortcut,
- SHORTCUT_FOCUS, w);
+ SHORTCUT_UCTRL, uc);
container = columns_new(4);
if (ctrl->listbox.percentwidth == 100) {
scs->sc[chr].action = action;
- if (action == SHORTCUT_FOCUS) {
+ if (action == SHORTCUT_FOCUS || action == SHORTCUT_TREE) {
scs->sc[chr].uc = NULL;
scs->sc[chr].widget = (GtkWidget *)ptr;
} else {
#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)
{
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);
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);
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 */
dlg_end(dlg, ctrl->generic.context.i);
}
int messagebox(GtkWidget *parentwin, const char *title, const char *msg,
- int minwid, ...)
+ int minwid, int selectable, ...)
{
GtkWidget *window, *w0, *w1;
struct controlbox *ctrlbox;
struct controlset *s0, *s1;
- union control *c;
+ union control *c, *textctrl;
struct dlgparam dp;
struct Shortcuts scs;
- int index, ncols;
+ int index, ncols, min_type;
va_list ap;
dlg_init(&dp);
ctrlbox = ctrl_new_box();
+ /*
+ * Preliminary pass over the va_list, to count up the number of
+ * buttons and find out what kinds there are.
+ */
ncols = 0;
- va_start(ap, minwid);
+ va_start(ap, selectable);
+ min_type = +1;
while (va_arg(ap, char *) != NULL) {
- ncols++;
+ int type;
+
(void) va_arg(ap, int); /* shortcut */
- (void) va_arg(ap, int); /* normal/default/cancel */
+ type = va_arg(ap, int); /* normal/default/cancel */
(void) va_arg(ap, int); /* end value */
+
+ ncols++;
+ if (min_type > type)
+ min_type = type;
}
va_end(ap);
c->columns.percentages = sresize(c->columns.percentages, ncols, int);
for (index = 0; index < ncols; index++)
c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols;
- va_start(ap, minwid);
+ va_start(ap, selectable);
index = 0;
while (1) {
char *title = va_arg(ap, char *);
c->generic.column = index++;
if (type > 0)
c->button.isdefault = TRUE;
- else if (type < 0)
+
+ /* We always arrange that _some_ button is labelled as
+ * 'iscancel', so that pressing Escape will always cause
+ * win_key_press to do something. The button we choose is
+ * whichever has the smallest type value: this means that real
+ * cancel buttons (labelled -1) will be picked if one is
+ * there, or in cases where the options are yes/no (1,0) then
+ * no will be picked, and if there's only one option (a box
+ * that really is just showing a _message_ and not even asking
+ * a question) then that will be picked. */
+ if (type == min_type)
c->button.iscancel = TRUE;
}
va_end(ap);
s1 = ctrl_getset(ctrlbox, "x", "", "");
- ctrl_text(s1, msg, HELPCTX(no_help));
+ textctrl = 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;
dp.retval = 0;
dp.window = window;
+ if (selectable) {
+#if GTK_CHECK_VERSION(2,0,0)
+ struct uctrl *uc = dlg_find_byctrl(&dp, textctrl);
+ gtk_label_set_selectable(GTK_LABEL(uc->text), TRUE);
+
+ /*
+ * GTK selectable labels have a habit of selecting their
+ * entire contents when they gain focus. It's ugly to have
+ * text in a message box start up all selected, so we suppress
+ * this by manually selecting none of it - but we must do this
+ * when the widget _already has_ focus, otherwise our work
+ * will be undone when it gains it shortly.
+ */
+ gtk_widget_grab_focus(uc->text);
+ gtk_label_select_region(GTK_LABEL(uc->text), 0, 0);
+#else
+ (void)textctrl; /* placate warning */
+#endif
+ }
+
gtk_window_set_modal(GTK_WINDOW(window), TRUE);
if (parentwin) {
set_transient_window_pos(parentwin, window);
GTK_WINDOW(parentwin));
} else
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+ gtk_container_set_focus_child(GTK_CONTAINER(window), NULL);
gtk_widget_show(window);
+ gtk_window_set_focus(GTK_WINDOW(window), NULL);
g_signal_connect(G_OBJECT(window), "destroy",
G_CALLBACK(window_destroy), NULL);
return dp.retval;
}
-int string_width(const char *text)
-{
- int ret;
- get_label_text_dimensions(text, &ret, NULL);
- return ret;
-}
-
int reallyclose(void *frontend)
{
char *title = dupcat(appname, " Exit Confirmation", NULL);
int ret = messagebox(GTK_WIDGET(get_window(frontend)),
title, "Are you sure you want to close this session?",
string_width("Most of the width of the above text"),
+ FALSE,
"Yes", 'y', +1, 1,
"No", 'n', -1, 0,
NULL);
ret = messagebox(GTK_WIDGET(get_window(frontend)),
"PuTTY Security Alert", text,
string_width(fingerprint),
+ TRUE,
"Accept", 'a', 0, 2,
"Connect Once", 'o', 0, 1,
"Cancel", 'c', -1, 0,
text = dupprintf(msg, algtype, algname);
ret = messagebox(GTK_WIDGET(get_window(frontend)),
"PuTTY Security Alert", text,
- string_width("Continue with connection?"),
+ string_width("Reasonably long line of text as a width"
+ " template"),
+ FALSE,
"Yes", 'y', 0, 1,
"No", 'n', 0, 0,
NULL);
{
messagebox(window, "PuTTY Fatal Error", msg,
string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
- "OK", 'o', 1, 1, NULL);
+ FALSE, "OK", 'o', 1, 1, NULL);
}
void nonfatal_message_box(void *window, const char *msg)
{
messagebox(window, "PuTTY Error", msg,
string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
- "OK", 'o', 1, 1, NULL);
+ FALSE, "OK", 'o', 1, 1, NULL);
}
void fatalbox(const char *p, ...)
aboutbox = NULL;
}
+static void about_key_press(GtkWidget *widget, GdkEventKey *event,
+ gpointer data)
+{
+ if (event->keyval == GDK_KEY_Escape && aboutbox) {
+ gtk_widget_destroy(aboutbox);
+ aboutbox = NULL;
+ }
+}
+
static void licence_clicked(GtkButton *button, gpointer data)
{
char *title;
messagebox(aboutbox, title, licence,
string_width("LONGISH LINE OF TEXT SO THE LICENCE"
" BOX ISN'T EXCESSIVELY TALL AND THIN"),
- "OK", 'o', 1, 1, NULL);
+ TRUE, "OK", 'o', 1, 1, NULL);
sfree(title);
}
void about_box(void *window)
{
GtkWidget *w;
+ GtkBox *action_area;
char *title;
if (aboutbox) {
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);
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);
- 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);
+ {
+ char *label_text = dupprintf
+ ("%s\n\n%s\n\n%s",
+ appname, ver,
+ "Copyright 1997-2015 Simon Tatham. All rights reserved");
+ w = gtk_label_new(label_text);
+ gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER);
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_label_set_selectable(GTK_LABEL(w), TRUE);
+#endif
+ sfree(label_text);
+ }
+ our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, FALSE, FALSE, 0);
+#if GTK_CHECK_VERSION(2,0,0)
+ /*
+ * Same precautions against initial select-all as in messagebox().
+ */
+ gtk_widget_grab_focus(w);
+ gtk_label_select_region(GTK_LABEL(w), 0, 0);
+#endif
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);
- gtk_widget_show(w);
+ g_signal_connect(G_OBJECT(aboutbox), "key_press_event",
+ G_CALLBACK(about_key_press), NULL);
set_transient_window_pos(GTK_WIDGET(window), aboutbox);
gtk_window_set_transient_for(GTK_WINDOW(aboutbox),
GTK_WINDOW(window));
+ gtk_container_set_focus_child(GTK_CONTAINER(aboutbox), NULL);
gtk_widget_show(aboutbox);
+ gtk_window_set_focus(GTK_WINDOW(aboutbox), NULL);
}
struct eventlog_stuff {
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);
("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;
mbret = messagebox(get_window(frontend), mbtitle, message,
string_width("LINE OF TEXT SUITABLE FOR THE"
" ASKAPPEND WIDTH"),
+ FALSE,
"Overwrite", 'o', 1, 2,
"Append", 'a', 0, 1,
"Disable", 'd', -1, 0,