]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - unix/gtkdlg.c
Better not forget to make sure GTK1 doesn't break.
[PuTTY.git] / unix / gtkdlg.c
index 267555a36a5167f63d6945cf449dcfd3d0f2a7ed..966164ac025e4ecf3a04bcc44802b5c907e70cfb 100644 (file)
@@ -2,18 +2,10 @@
  * gtkdlg.c - GTK implementation of the PuTTY configuration box.
  */
 
-/*
- * TODO when porting to GTK 2.0:
- * 
- *  - GtkTree is apparently deprecated and we should switch to
- *    GtkTreeView instead.
- *  - GtkLabel has a built-in mnemonic scheme, so we should at
- *    least consider switching to that from the current adhockery.
- */
-
 #include <assert.h>
 #include <stdarg.h>
 #include <ctype.h>
+#include <time.h>
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
 #include <gdk/gdkx.h>
@@ -21,7 +13,6 @@
 #include <X11/Xutil.h>
 
 #include "gtkcols.h"
-#include "gtkpanel.h"
 
 #ifdef TESTMODE
 #define PUTTY_DO_GLOBALS              /* actually _define_ globals */
@@ -54,7 +45,9 @@ struct uctrl {
     GtkWidget *menu;         /* for optionmenu (==droplist) */
     GtkWidget *optmenu;              /* also for optionmenu */
     GtkWidget *text;         /* for text */
+    GtkWidget *label;         /* for dlg_label_change */
     GtkAdjustment *adj;       /* for the scrollbar in a list box */
+    guint entrysig;
     guint textsig;
 };
 
@@ -66,9 +59,12 @@ struct dlgparam {
      * due to automatic processing and should not flag a user event. */
     int flags;
     struct Shortcuts *shortcuts;
-    GtkWidget *window, *cancelbutton, *currtreeitem, **treeitems;
+    GtkWidget *window, *cancelbutton;
     union control *currfocus, *lastfocus;
+#if !GTK_CHECK_VERSION(2,0,0)
+    GtkWidget *currtreeitem, **treeitems;
     int ntreeitems;
+#endif
     int retval;
 };
 #define FLAG_UPDATING_COMBO_LIST 1
@@ -82,6 +78,14 @@ enum {                                      /* values for Shortcut.action */
     SHORTCUT_UCTRL_DOWN,              /* uctrl is a draglist, move Down */
 };
 
+#if GTK_CHECK_VERSION(2,0,0)
+enum {
+    TREESTORE_PATH,
+    TREESTORE_PARAMS,
+    TREESTORE_NUM
+};
+#endif
+
 /*
  * Forward references.
  */
@@ -89,12 +93,13 @@ static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
                              gpointer data);
 static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
                         int chr, int action, void *ptr);
-static int listitem_single_key(GtkWidget *item, GdkEventKey *event,
-                               gpointer data);
-static int listitem_multi_key(GtkWidget *item, GdkEventKey *event,
-                                 gpointer data);
-static int listitem_button(GtkWidget *item, GdkEventButton *event,
-                           gpointer data);
+static void shortcut_highlight(GtkWidget *label, int chr);
+static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
+                                   gpointer data);
+static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
+                                  gpointer data);
+static gboolean listitem_button(GtkWidget *item, GdkEventButton *event,
+                               gpointer data);
 static void menuitem_activate(GtkMenuItem *item, gpointer data);
 static void coloursel_ok(GtkButton *button, gpointer data);
 static void coloursel_cancel(GtkButton *button, gpointer data);
@@ -149,8 +154,13 @@ static void dlg_init(struct dlgparam *dp)
     dp->byctrl = newtree234(uctrl_cmp_byctrl);
     dp->bywidget = newtree234(uctrl_cmp_bywidget);
     dp->coloursel_result.ok = FALSE;
+    dp->window = dp->cancelbutton = NULL;
+#if !GTK_CHECK_VERSION(2,0,0)
     dp->treeitems = NULL;
-    dp->window = dp->cancelbutton = dp->currtreeitem = NULL;
+    dp->currtreeitem = NULL;
+#endif
+    dp->flags = 0;
+    dp->currfocus = NULL;
 }
 
 static void dlg_cleanup(struct dlgparam *dp)
@@ -158,6 +168,7 @@ static void dlg_cleanup(struct dlgparam *dp)
     struct uctrl *uc;
 
     freetree234(dp->byctrl);          /* doesn't free the uctrls inside */
+    dp->byctrl = NULL;
     while ( (uc = index234(dp->bywidget, 0)) != NULL) {
        del234(dp->bywidget, uc);
        if (uc->privdata_needs_free)
@@ -166,7 +177,10 @@ static void dlg_cleanup(struct dlgparam *dp)
        sfree(uc);
     }
     freetree234(dp->bywidget);
+    dp->bywidget = NULL;
+#if !GTK_CHECK_VERSION(2,0,0)
     sfree(dp->treeitems);
+#endif
 }
 
 static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc)
@@ -177,12 +191,16 @@ static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc)
 
 static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, union control *ctrl)
 {
+    if (!dp->byctrl)
+       return NULL;
     return find234(dp->byctrl, ctrl, uctrl_cmp_byctrl_find);
 }
 
 static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w)
 {
     struct uctrl *ret = NULL;
+    if (!dp->bywidget)
+       return NULL;
     do {
        ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find);
        if (ret)
@@ -274,7 +292,27 @@ void dlg_editbox_set(union control *ctrl, void *dlg, char const *text)
     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
     assert(uc->ctrl->generic.type == CTRL_EDITBOX);
     assert(uc->entry != NULL);
+    /*
+     * GTK 2 implements gtk_entry_set_text by means of two separate
+     * operations: first delete the previous text leaving the empty
+     * string, then insert the new text. This causes two calls to
+     * the "changed" signal.
+     * 
+     * The first call to "changed", if allowed to proceed normally,
+     * will cause an EVENT_VALCHANGE event on the edit box, causing
+     * a call to dlg_editbox_get() which will read the empty string
+     * out of the GtkEntry - and promptly write it straight into
+     * the Config structure, which is precisely where our `text'
+     * pointer is probably pointing, so the second editing
+     * operation will insert that instead of the string we
+     * originally asked for.
+     * 
+     * Hence, we must block our "changed" signal handler for the
+     * duration of this call to gtk_entry_set_text.
+     */
+    g_signal_handler_block(uc->entry, uc->entrysig);
     gtk_entry_set_text(GTK_ENTRY(uc->entry), text);
+    g_signal_handler_unblock(uc->entry, uc->entrysig);
 }
 
 void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length)
@@ -300,15 +338,18 @@ void dlg_listbox_clear(union control *ctrl, void *dlg)
 {
     struct dlgparam *dp = (struct dlgparam *)dlg;
     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-    GtkContainer *cont;
 
     assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
           uc->ctrl->generic.type == CTRL_LISTBOX);
     assert(uc->menu != NULL || uc->list != NULL);
 
-    cont = (uc->menu ? GTK_CONTAINER(uc->menu) : GTK_CONTAINER(uc->list));
-
-    gtk_container_foreach(cont, container_remove_and_destroy, cont);
+    if (uc->menu) {
+       gtk_container_foreach(GTK_CONTAINER(uc->menu),
+                             container_remove_and_destroy,
+                             GTK_CONTAINER(uc->menu));
+    } else {
+       gtk_list_clear_items(GTK_LIST(uc->list), 0, -1);
+    }
 }
 
 void dlg_listbox_del(union control *ctrl, void *dlg, int index)
@@ -364,7 +405,8 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg,
        gtk_container_add(GTK_CONTAINER(uc->menu), menuitem);
        gtk_widget_show(menuitem);
 
-       gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", (gpointer)id);
+       gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+                           GINT_TO_POINTER(id));
        gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
                           GTK_SIGNAL_FUNC(menuitem_activate), dp);
     } else if (!uc->entry) {
@@ -426,7 +468,8 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg,
                            GTK_SIGNAL_FUNC(widget_focus), dp);
        gtk_signal_connect(GTK_OBJECT(listitem), "button_press_event",
                           GTK_SIGNAL_FUNC(listitem_button), dp);
-       gtk_object_set_data(GTK_OBJECT(listitem), "user-data", (gpointer)id);
+       gtk_object_set_data(GTK_OBJECT(listitem), "user-data",
+                           GINT_TO_POINTER(id));
     } else {
        /*
         * List item in a combo-box list, which means the sensible
@@ -438,7 +481,8 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg,
        gtk_container_add(GTK_CONTAINER(uc->list), listitem);
        gtk_widget_show(listitem);
 
-       gtk_object_set_data(GTK_OBJECT(listitem), "user-data", (gpointer)id);
+       gtk_object_set_data(GTK_OBJECT(listitem), "user-data",
+                           GINT_TO_POINTER(id));
     }
 
     dp->flags &= ~FLAG_UPDATING_COMBO_LIST;
@@ -460,7 +504,7 @@ int dlg_listbox_getid(union control *ctrl, void *dlg, int index)
     item = GTK_OBJECT(g_list_nth_data(children, index));
     g_list_free(children);
 
-    return (int)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+    return GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), "user-data"));
 }
 
 /* dlg_listbox_index returns <0 if no single element is selected. */
@@ -479,6 +523,8 @@ int dlg_listbox_index(union control *ctrl, void *dlg)
 
     if (uc->menu)
        activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
+    else
+       activeitem = NULL;             /* unnecessarily placate gcc */
 
     children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
                                                    uc->list));
@@ -532,7 +578,35 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index)
     if (uc->optmenu) {
        gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index);
     } else {
+        int nitems;
+        GList *items;
+        gdouble newtop, newbot;
+
        gtk_list_select_item(GTK_LIST(uc->list), index);
+
+        /*
+         * Scroll the list box if necessary to ensure the newly
+         * selected item is visible.
+         */
+        items = gtk_container_children(GTK_CONTAINER(uc->list));
+        nitems = g_list_length(items);
+        if (nitems > 0) {
+            int modified = FALSE;
+            g_list_free(items);
+            newtop = uc->adj->lower +
+                (uc->adj->upper - uc->adj->lower) * index / nitems;
+            newbot = uc->adj->lower +
+                (uc->adj->upper - uc->adj->lower) * (index+1) / nitems;
+            if (uc->adj->value > newtop) {
+                modified = TRUE;
+                uc->adj->value = newtop;
+            } else if (uc->adj->value < newbot - uc->adj->page_size) {
+                modified = TRUE;
+                uc->adj->value = newbot - uc->adj->page_size;
+            }
+            if (modified)
+                gtk_adjustment_value_changed(uc->adj);
+        }
     }
 }
 
@@ -547,6 +621,46 @@ void dlg_text_set(union control *ctrl, void *dlg, char const *text)
     gtk_label_set_text(GTK_LABEL(uc->text), text);
 }
 
+void dlg_label_change(union control *ctrl, void *dlg, char const *text)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+    switch (uc->ctrl->generic.type) {
+      case CTRL_BUTTON:
+       gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
+       shortcut_highlight(uc->toplevel, ctrl->button.shortcut);
+       break;
+      case CTRL_CHECKBOX:
+       gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
+       shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut);
+       break;
+      case CTRL_RADIO:
+       gtk_label_set_text(GTK_LABEL(uc->label), text);
+       shortcut_highlight(uc->label, ctrl->radio.shortcut);
+       break;
+      case CTRL_EDITBOX:
+       gtk_label_set_text(GTK_LABEL(uc->label), text);
+       shortcut_highlight(uc->label, ctrl->editbox.shortcut);
+       break;
+      case CTRL_FILESELECT:
+       gtk_label_set_text(GTK_LABEL(uc->label), text);
+       shortcut_highlight(uc->label, ctrl->fileselect.shortcut);
+       break;
+      case CTRL_FONTSELECT:
+       gtk_label_set_text(GTK_LABEL(uc->label), text);
+       shortcut_highlight(uc->label, ctrl->fontselect.shortcut);
+       break;
+      case CTRL_LISTBOX:
+       gtk_label_set_text(GTK_LABEL(uc->label), text);
+       shortcut_highlight(uc->label, ctrl->listbox.shortcut);
+       break;
+      default:
+       assert(!"This shouldn't happen");
+       break;
+    }
+}
+
 void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn)
 {
     struct dlgparam *dp = (struct dlgparam *)dlg;
@@ -649,7 +763,11 @@ void dlg_set_focus(union control *ctrl, void *dlg)
             gtk_widget_grab_focus(uc->optmenu);
         } else {
             assert(uc->list != NULL);
+#if GTK_CHECK_VERSION(2,0,0)
+           gtk_widget_grab_focus(uc->list);
+#else
             gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD);
+#endif
         }
         break;
     }
@@ -670,6 +788,35 @@ static void errmsg_button_clicked(GtkButton *button, gpointer data)
     gtk_widget_destroy(GTK_WIDGET(data));
 }
 
+static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child)
+{
+    gint x, y, w, h, dx, dy;
+    GtkRequisition req;
+    gtk_window_set_position(GTK_WINDOW(child), GTK_WIN_POS_NONE);
+    gtk_widget_size_request(GTK_WIDGET(child), &req);
+
+    gdk_window_get_origin(GTK_WIDGET(parent)->window, &x, &y);
+    gdk_window_get_size(GTK_WIDGET(parent)->window, &w, &h);
+
+    /*
+     * One corner of the transient will be offset inwards, by 1/4
+     * of the parent window's size, from the corresponding corner
+     * of the parent window. The corner will be chosen so as to
+     * place the transient closer to the centre of the screen; this
+     * should avoid transients going off the edge of the screen on
+     * a regular basis.
+     */
+    if (x + w/2 < gdk_screen_width() / 2)
+        dx = x + w/4;                  /* work from left edges */
+    else
+        dx = x + 3*w/4 - req.width;    /* work from right edges */
+    if (y + h/2 < gdk_screen_height() / 2)
+        dy = y + h/4;                  /* work from top edges */
+    else
+        dy = y + 3*h/4 - req.height;   /* work from bottom edges */
+    gtk_widget_set_uposition(GTK_WIDGET(child), dx, dy);
+}
+
 void dlg_error_msg(void *dlg, char *msg)
 {
     struct dlgparam *dp = (struct dlgparam *)dlg;
@@ -698,15 +845,7 @@ void dlg_error_msg(void *dlg, char *msg)
                        GTK_SIGNAL_FUNC(window_destroy), NULL);
     gtk_window_set_modal(GTK_WINDOW(window), TRUE);
     gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(dp->window));
-    {
-       gint x, y, w, h, dx, dy;
-       gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_NONE);
-       gdk_window_get_origin(GTK_WIDGET(dp->window)->window, &x, &y);
-       gdk_window_get_size(GTK_WIDGET(dp->window)->window, &w, &h);
-       dx = x + w/4;
-       dy = y + h/4;
-       gtk_widget_set_uposition(GTK_WIDGET(window), dx, dy);
-    }
+    set_transient_window_pos(dp->window, window);
     gtk_widget_show(window);
     gtk_main();
 }
@@ -756,7 +895,11 @@ void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b)
     dp->coloursel_result.ok = FALSE;
 
     gtk_window_set_modal(GTK_WINDOW(coloursel), TRUE);
+#if GTK_CHECK_VERSION(2,0,0)
+    gtk_color_selection_set_has_opacity_control(GTK_COLOR_SELECTION(ccs->colorsel), FALSE);
+#else
     gtk_color_selection_set_opacity(GTK_COLOR_SELECTION(ccs->colorsel), FALSE);
+#endif
     cvals[0] = r / 255.0;
     cvals[1] = g / 255.0;
     cvals[2] = b / 255.0;
@@ -832,7 +975,8 @@ static void button_toggled(GtkToggleButton *tb, gpointer data)
     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
 }
 
-static int editbox_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
+static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event,
+                           gpointer data)
 {
     /*
      * GtkEntry has a nasty habit of eating the Return key, which
@@ -844,7 +988,7 @@ static int editbox_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
      * in the dialog just like it will everywhere else.
      */
     if (event->keyval == GDK_Return && widget->parent != NULL) {
-       gint return_val;
+       gboolean return_val;
        gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
        gtk_signal_emit_by_name(GTK_OBJECT(widget->parent), "key_press_event",
                                event, &return_val);
@@ -862,16 +1006,17 @@ static void editbox_changed(GtkEditable *ed, gpointer data)
     }
 }
 
-static void editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event,
-                             gpointer data)
+static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event,
+                                 gpointer data)
 {
     struct dlgparam *dp = (struct dlgparam *)data;
     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_REFRESH);
+    return FALSE;
 }
 
-static int listitem_key(GtkWidget *item, GdkEventKey *event, gpointer data,
-                        int multiple)
+static gboolean listitem_key(GtkWidget *item, GdkEventKey *event,
+                            gpointer data, int multiple)
 {
     GtkAdjustment *adj = GTK_ADJUSTMENT(data);
 
@@ -964,20 +1109,20 @@ static int listitem_key(GtkWidget *item, GdkEventKey *event, gpointer data,
     return FALSE;
 }
 
-static int listitem_single_key(GtkWidget *item, GdkEventKey *event,
-                               gpointer data)
+static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
+                                   gpointer data)
 {
     return listitem_key(item, event, data, FALSE);
 }
 
-static int listitem_multi_key(GtkWidget *item, GdkEventKey *event,
-                                 gpointer data)
+static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
+                                  gpointer data)
 {
     return listitem_key(item, event, data, TRUE);
 }
 
-static int listitem_button(GtkWidget *item, GdkEventButton *event,
-                           gpointer data)
+static gboolean listitem_button(GtkWidget *item, GdkEventButton *event,
+                               gpointer data)
 {
     struct dlgparam *dp = (struct dlgparam *)data;
     if (event->type == GDK_2BUTTON_PRESS ||
@@ -1050,7 +1195,8 @@ static void filesel_ok(GtkButton *button, gpointer data)
     /* struct dlgparam *dp = (struct dlgparam *)data; */
     gpointer filesel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
     struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(filesel), "user-data");
-    char *name = gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel));
+    const char *name = gtk_file_selection_get_filename
+       (GTK_FILE_SELECTION(filesel));
     gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
 }
 
@@ -1059,7 +1205,7 @@ static void fontsel_ok(GtkButton *button, gpointer data)
     /* struct dlgparam *dp = (struct dlgparam *)data; */
     gpointer fontsel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
     struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(fontsel), "user-data");
-    char *name = gtk_font_selection_dialog_get_font_name
+    const char *name = gtk_font_selection_dialog_get_font_name
        (GTK_FONT_SELECTION_DIALOG(fontsel));
     gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
 }
@@ -1115,15 +1261,20 @@ static void filefont_clicked(GtkButton *button, gpointer data)
     }
 
     if (uc->ctrl->generic.type == CTRL_FONTSELECT) {
+#if !GTK_CHECK_VERSION(2,0,0)
        gchar *spacings[] = { "c", "m", NULL };
-        gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry));
+#endif
+        const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry));
+       /* TODO: In GTK 2, this only seems to offer client-side fonts. */
        GtkWidget *fontsel =
            gtk_font_selection_dialog_new("Select a font");
        gtk_window_set_modal(GTK_WINDOW(fontsel), TRUE);
+#if !GTK_CHECK_VERSION(2,0,0)
        gtk_font_selection_dialog_set_filter
            (GTK_FONT_SELECTION_DIALOG(fontsel),
             GTK_FONT_FILTER_BASE, GTK_FONT_ALL,
             NULL, NULL, NULL, NULL, spacings, NULL);
+#endif
        if (!gtk_font_selection_dialog_set_font_name
            (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) {
             /*
@@ -1138,6 +1289,7 @@ static void filefont_clicked(GtkButton *button, gpointer data)
                 Display *disp = GDK_FONT_XDISPLAY(font);
                 Atom fontprop = XInternAtom(disp, "FONT", False);
                 unsigned long ret;
+               gdk_font_ref(font);
                 if (XGetFontProperty(xfs, fontprop, &ret)) {
                     char *name = XGetAtomName(disp, (Atom)ret);
                     if (name)
@@ -1259,6 +1411,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
        uc->buttons = NULL;
        uc->entry = uc->list = uc->menu = NULL;
        uc->button = uc->optmenu = uc->text = NULL;
+       uc->label = NULL;
 
         switch (ctrl->generic.type) {
           case CTRL_BUTTON:
@@ -1304,6 +1457,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                     gtk_widget_show(label);
                    shortcut_add(scs, label, ctrl->radio.shortcut,
                                 SHORTCUT_UCTRL, uc);
+                   uc->label = label;
                 }
                 percentages = g_new(gint, ctrl->radio.ncolumns);
                 for (i = 0; i < ctrl->radio.ncolumns; i++) {
@@ -1360,8 +1514,9 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                        gtk_entry_set_visibility(GTK_ENTRY(w), FALSE);
                    uc->entry = w;
                }
-               gtk_signal_connect(GTK_OBJECT(uc->entry), "changed",
-                                  GTK_SIGNAL_FUNC(editbox_changed), dp);
+               uc->entrysig =
+                   gtk_signal_connect(GTK_OBJECT(uc->entry), "changed",
+                                      GTK_SIGNAL_FUNC(editbox_changed), dp);
                gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event",
                                   GTK_SIGNAL_FUNC(editbox_key), dp);
                gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event",
@@ -1408,6 +1563,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                    gtk_widget_show(w);
 
                    w = container;
+                   uc->label = label;
                }
                gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_out_event",
                                   GTK_SIGNAL_FUNC(editbox_lostfocus), dp);
@@ -1436,6 +1592,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                                  ctrl->fileselect.shortcut :
                                  ctrl->fontselect.shortcut),
                                 SHORTCUT_UCTRL, uc);
+                   uc->label = ww;
                 }
 
                 uc->entry = ww = gtk_entry_new();
@@ -1450,8 +1607,9 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
 
                gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event",
                                   GTK_SIGNAL_FUNC(editbox_key), dp);
-               gtk_signal_connect(GTK_OBJECT(uc->entry), "changed",
-                                  GTK_SIGNAL_FUNC(editbox_changed), dp);
+               uc->entrysig =
+                   gtk_signal_connect(GTK_OBJECT(uc->entry), "changed",
+                                      GTK_SIGNAL_FUNC(editbox_changed), dp);
                 gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event",
                                    GTK_SIGNAL_FUNC(widget_focus), dp);
                 gtk_signal_connect(GTK_OBJECT(uc->button), "focus_in_event",
@@ -1471,7 +1629,10 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                                    GTK_SIGNAL_FUNC(widget_focus), dp);
             } else {
                 uc->list = gtk_list_new();
-                if (ctrl->listbox.multisel) {
+                if (ctrl->listbox.multisel == 2) {
+                    gtk_list_set_selection_mode(GTK_LIST(uc->list),
+                                                GTK_SELECTION_EXTENDED);
+                } else if (ctrl->listbox.multisel == 1) {
                     gtk_list_set_selection_mode(GTK_LIST(uc->list),
                                                 GTK_SELECTION_MULTIPLE);
                 } else {
@@ -1506,7 +1667,12 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                  * upgrades, I'd be grateful.
                  */
                {
-                   int edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
+                   int edge;
+#if GTK_CHECK_VERSION(2,0,0)
+                   edge = GTK_WIDGET(uc->list)->style->ythickness;
+#else
+                   edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
+#endif
                     gtk_widget_set_usize(w, 10,
                                          2*edge + (ctrl->listbox.height *
                                                   listitemheight));
@@ -1570,6 +1736,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                shortcut_add(scs, label, ctrl->listbox.shortcut,
                             SHORTCUT_UCTRL, uc);
                 w = container;
+               uc->label = label;
             }
             break;
           case CTRL_TEXT:
@@ -1625,26 +1792,59 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
 
 struct selparam {
     struct dlgparam *dp;
-    Panels *panels;
-    GtkWidget *panel, *treeitem;
+    GtkNotebook *panels;
+    GtkWidget *panel;
+#if !GTK_CHECK_VERSION(2,0,0)
+    GtkWidget *treeitem;
+#endif
     struct Shortcuts shortcuts;
 };
 
+#if GTK_CHECK_VERSION(2,0,0)
+static void treeselection_changed(GtkTreeSelection *treeselection,
+                                 gpointer data)
+{
+    struct selparam *sps = (struct selparam *)data, *sp;
+    GtkTreeModel *treemodel;
+    GtkTreeIter treeiter;
+    gint spindex;
+    gint page_num;
+
+    if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
+       return;
+
+    gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1);
+    sp = &sps[spindex];
+
+    page_num = gtk_notebook_page_num(sp->panels, sp->panel);
+    gtk_notebook_set_page(sp->panels, page_num);
+
+    dlg_refresh(NULL, sp->dp);
+
+    sp->dp->shortcuts = &sp->shortcuts;
+}
+#else
 static void treeitem_sel(GtkItem *item, gpointer data)
 {
     struct selparam *sp = (struct selparam *)data;
+    gint page_num;
+
+    page_num = gtk_notebook_page_num(sp->panels, sp->panel);
+    gtk_notebook_set_page(sp->panels, page_num);
 
-    panels_switch_to(sp->panels, sp->panel);
+    dlg_refresh(NULL, sp->dp);
 
     sp->dp->shortcuts = &sp->shortcuts;
     sp->dp->currtreeitem = sp->treeitem;
 }
+#endif
 
 static void window_destroy(GtkWidget *widget, gpointer data)
 {
     gtk_main_quit();
 }
 
+#if !GTK_CHECK_VERSION(2,0,0)
 static int tree_grab_focus(struct dlgparam *dp)
 {
     int i, f;
@@ -1680,6 +1880,7 @@ gint tree_focus(GtkContainer *container, GtkDirectionType direction,
      */
     return tree_grab_focus(dp);
 }
+#endif
 
 int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
@@ -1698,7 +1899,11 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
 
        switch (sc->action) {
          case SHORTCUT_TREE:
+#if GTK_CHECK_VERSION(2,0,0)
+           gtk_widget_grab_focus(sc->widget);
+#else
            tree_grab_focus(dp);
+#endif
            break;
          case SHORTCUT_FOCUS:
            gtk_widget_grab_focus(sc->widget);
@@ -1774,8 +1979,12 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
                } else {
                     assert(sc->uc->list != NULL);
 
+#if GTK_CHECK_VERSION(2,0,0)
+                   gtk_widget_grab_focus(sc->uc->list);
+#else
                     gtk_container_focus(GTK_CONTAINER(sc->uc->list),
                                         GTK_DIR_TAB_FORWARD);
+#endif
                }
                break;
            }
@@ -1786,6 +1995,7 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
     return FALSE;
 }
 
+#if !GTK_CHECK_VERSION(2,0,0)
 int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
     struct dlgparam *dp = (struct dlgparam *)data;
@@ -1854,14 +2064,33 @@ int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
 
     return FALSE;
 }
+#endif
 
-void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
-                 int chr, int action, void *ptr)
+static void shortcut_highlight(GtkWidget *labelw, int chr)
 {
     GtkLabel *label = GTK_LABEL(labelw);
     gchar *currstr, *pattern;
     int i;
 
+    gtk_label_get(label, &currstr);
+    for (i = 0; currstr[i]; i++)
+       if (tolower((unsigned char)currstr[i]) == chr) {
+           GtkRequisition req;
+
+           pattern = dupprintf("%*s_", i, "");
+
+           gtk_widget_size_request(GTK_WIDGET(label), &req);
+           gtk_label_set_pattern(label, pattern);
+           gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height);
+
+           sfree(pattern);
+           break;
+       }
+}
+
+void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
+                 int chr, int action, void *ptr)
+{
     if (chr == NO_SHORTCUT)
        return;
 
@@ -1879,33 +2108,103 @@ void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
        scs->sc[chr].uc = (struct uctrl *)ptr;
     }
 
-    gtk_label_get(label, &currstr);
-    for (i = 0; currstr[i]; i++)
-       if (tolower((unsigned char)currstr[i]) == chr) {
-           GtkRequisition req;
+    shortcut_highlight(labelw, chr);
+}
 
-           pattern = dupprintf("%*s_", i, "");
+int get_listitemheight(void)
+{
+    GtkWidget *listitem = gtk_list_item_new_with_label("foo");
+    GtkRequisition req;
+    gtk_widget_size_request(listitem, &req);
+    gtk_object_sink(GTK_OBJECT(listitem));
+    return req.height;
+}
 
-           gtk_widget_size_request(GTK_WIDGET(label), &req);
-           gtk_label_set_pattern(label, pattern);
-           gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height);
+void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w)
+{
+#if !GTK_CHECK_VERSION(2,0,0)
 
-           sfree(pattern);
-           break;
-       }
+    /*
+     * 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(GTK_DIALOG(window)->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.)
+     */
+
+    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).
+     */
+    gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8);
+    gtk_widget_show(align);
+    gtk_box_pack_end(GTK_BOX(dlg->vbox), align, FALSE, TRUE, 0);
+    w = gtk_hseparator_new();
+    gtk_box_pack_end(GTK_BOX(dlg->vbox), w, FALSE, TRUE, 0);
+    gtk_widget_show(w);
+    gtk_widget_hide(dlg->action_area);
+    gtk_dialog_set_has_separator(dlg, FALSE);
+#endif
 }
 
-int do_config_box(const char *title, Config *cfg)
+int do_config_box(const char *title, Config *cfg, int midsession,
+                 int protcfginfo)
 {
     GtkWidget *window, *hbox, *vbox, *cols, *label,
        *tree, *treescroll, *panels, *panelvbox;
     int index, level, listitemheight;
     struct controlbox *ctrlbox;
     char *path;
+#if GTK_CHECK_VERSION(2,0,0)
+    GtkTreeStore *treestore;
+    GtkCellRenderer *treerenderer;
+    GtkTreeViewColumn *treecolumn;
+    GtkTreeSelection *treeselection;
+    GtkTreeIter treeiterlevels[8];
+#else
     GtkTreeItem *treeitemlevels[8];
     GtkTree *treelevels[8];
+#endif
     struct dlgparam dp;
-    struct sesslist sl;
     struct Shortcuts scs;
 
     struct selparam *selparams = NULL;
@@ -1913,25 +2212,19 @@ int do_config_box(const char *title, Config *cfg)
 
     dlg_init(&dp);
 
-    {
-        GtkWidget *listitem = gtk_list_item_new_with_label("foo");
-        GtkRequisition req;
-        gtk_widget_size_request(listitem, &req);
-        listitemheight = req.height;
-        gtk_widget_unref(listitem);
-    }
-
-    get_sesslist(&sl, TRUE);
+    listitemheight = get_listitemheight();
 
     for (index = 0; index < lenof(scs.sc); index++) {
        scs.sc[index].action = SHORTCUT_EMPTY;
     }
 
+    window = gtk_dialog_new();
+
     ctrlbox = ctrl_new_box();
-    setup_config_box(ctrlbox, &sl, FALSE, 0);
-    unix_setup_config_box(ctrlbox, FALSE);
+    setup_config_box(ctrlbox, midsession, cfg->protocol, protcfginfo);
+    unix_setup_config_box(ctrlbox, midsession, cfg->protocol);
+    gtk_setup_config_box(ctrlbox, midsession, window);
 
-    window = gtk_dialog_new();
     gtk_window_set_title(GTK_WINDOW(window), title);
     hbox = gtk_hbox_new(FALSE, 4);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), hbox, TRUE, TRUE, 0);
@@ -1948,23 +2241,33 @@ int do_config_box(const char *title, Config *cfg)
     columns_force_left_align(COLUMNS(cols), label);
     gtk_widget_show(label);
     treescroll = gtk_scrolled_window_new(NULL, NULL);
+#if GTK_CHECK_VERSION(2,0,0)
+    treestore = gtk_tree_store_new
+       (TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT);
+    tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore));
+    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
+    treerenderer = gtk_cell_renderer_text_new();
+    treecolumn = gtk_tree_view_column_new_with_attributes
+       ("Label", treerenderer, "text", 0, NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn);
+    treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
+    gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE);
+    gtk_container_add(GTK_CONTAINER(treescroll), tree);
+#else
     tree = gtk_tree_new();
-    gtk_signal_connect(GTK_OBJECT(tree), "focus_in_event",
-                       GTK_SIGNAL_FUNC(widget_focus), &dp);
-    shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree);
     gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
     gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
-    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
-                                         tree);
-    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
-                                  GTK_POLICY_NEVER,
-                                  GTK_POLICY_AUTOMATIC);
     gtk_signal_connect(GTK_OBJECT(tree), "focus",
                       GTK_SIGNAL_FUNC(tree_focus), &dp);
-    gtk_widget_show(tree);
+#endif
+    gtk_signal_connect(GTK_OBJECT(tree), "focus_in_event",
+                       GTK_SIGNAL_FUNC(widget_focus), &dp);
+    shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree);
     gtk_widget_show(treescroll);
     gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0);
-    panels = panels_new();
+    panels = gtk_notebook_new();
+    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(panels), FALSE);
+    gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), FALSE);
     gtk_box_pack_start(GTK_BOX(hbox), panels, TRUE, TRUE, 0);
     gtk_widget_show(panels);
 
@@ -1977,13 +2280,17 @@ int do_config_box(const char *title, Config *cfg)
 
        if (!*s->pathname) {
            w = layout_ctrls(&dp, &scs, s, listitemheight, GTK_WINDOW(window));
-           gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->action_area),
-                              w, TRUE, TRUE, 0);
+
+           set_dialog_action_area(GTK_DIALOG(window), w);
        } else {
            int j = path ? ctrl_path_compare(s->pathname, path) : 0;
            if (j != INT_MAX) {        /* add to treeview, start new panel */
                char *c;
+#if GTK_CHECK_VERSION(2,0,0)
+               GtkTreeIter treeiter;
+#else
                GtkWidget *treeitem;
+#endif
                int first;
 
                /*
@@ -2004,15 +2311,73 @@ int do_config_box(const char *title, Config *cfg)
                else
                    c++;
 
-               treeitem = gtk_tree_item_new_with_label(c);
+               path = s->pathname;
+
+               first = (panelvbox == NULL);
+
+               panelvbox = gtk_vbox_new(FALSE, 4);
+               gtk_widget_show(panelvbox);
+               gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox,
+                                        NULL);
+               if (first) {
+                   gint page_num;
+
+                   page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels),
+                                                    panelvbox);
+                   gtk_notebook_set_page(GTK_NOTEBOOK(panels), page_num);
+               }
+
+               if (nselparams >= selparamsize) {
+                   selparamsize += 16;
+                   selparams = sresize(selparams, selparamsize,
+                                       struct selparam);
+               }
+               selparams[nselparams].dp = &dp;
+               selparams[nselparams].panels = GTK_NOTEBOOK(panels);
+               selparams[nselparams].panel = panelvbox;
+               selparams[nselparams].shortcuts = scs;   /* structure copy */
+
                assert(j-1 < level);
+
+#if GTK_CHECK_VERSION(2,0,0)
+               if (j > 0)
+                   /* treeiterlevels[j-1] will always be valid because we
+                    * don't allow implicit path components; see above.
+                    */
+                   gtk_tree_store_append(treestore, &treeiter,
+                                         &treeiterlevels[j-1]);
+               else
+                   gtk_tree_store_append(treestore, &treeiter, NULL);
+               gtk_tree_store_set(treestore, &treeiter,
+                                  TREESTORE_PATH, c,
+                                  TREESTORE_PARAMS, nselparams,
+                                  -1);
+               treeiterlevels[j] = treeiter;
+
+               if (j > 0) {
+                   GtkTreePath *path;
+
+                   path = gtk_tree_model_get_path(GTK_TREE_MODEL(treestore),
+                                                  &treeiterlevels[j-1]);
+                   if (j < 2)
+                       gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), path,
+                                                FALSE);
+                   else
+                       gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), path);
+                   gtk_tree_path_free(path);
+               }
+#else
+               treeitem = gtk_tree_item_new_with_label(c);
                if (j > 0) {
                    if (!treelevels[j-1]) {
                        treelevels[j-1] = GTK_TREE(gtk_tree_new());
                        gtk_tree_item_set_subtree
                            (treeitemlevels[j-1],
                             GTK_WIDGET(treelevels[j-1]));
-                       gtk_tree_item_expand(treeitemlevels[j-1]);
+                        if (j < 2)
+                            gtk_tree_item_expand(treeitemlevels[j-1]);
+                        else
+                            gtk_tree_item_collapse(treeitemlevels[j-1]);
                    }
                    gtk_tree_append(treelevels[j-1], treeitem);
                } else {
@@ -2020,7 +2385,6 @@ int do_config_box(const char *title, Config *cfg)
                }
                treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
                treelevels[j] = NULL;
-               level = j+1;
 
                 gtk_signal_connect(GTK_OBJECT(treeitem), "key_press_event",
                                    GTK_SIGNAL_FUNC(tree_key_press), &dp);
@@ -2029,29 +2393,13 @@ int do_config_box(const char *title, Config *cfg)
 
                gtk_widget_show(treeitem);
 
-               path = s->pathname;
-
-               first = (panelvbox == NULL);
-
-               panelvbox = gtk_vbox_new(FALSE, 4);
-               gtk_container_add(GTK_CONTAINER(panels), panelvbox);
-               if (first) {
-                   panels_switch_to(PANELS(panels), panelvbox);
+               if (first)
                    gtk_tree_select_child(GTK_TREE(tree), treeitem);
-               }
-
-               if (nselparams >= selparamsize) {
-                   selparamsize += 16;
-                   selparams = sresize(selparams, selparamsize,
-                                       struct selparam);
-               }
-               selparams[nselparams].dp = &dp;
-               selparams[nselparams].panels = PANELS(panels);
-               selparams[nselparams].panel = panelvbox;
-               selparams[nselparams].shortcuts = scs;   /* structure copy */
                selparams[nselparams].treeitem = treeitem;
-               nselparams++;
+#endif
 
+               level = j+1;
+               nselparams++;
            }
 
            w = layout_ctrls(&dp,
@@ -2062,6 +2410,10 @@ int do_config_box(const char *title, Config *cfg)
        }
     }
 
+#if GTK_CHECK_VERSION(2,0,0)
+    g_signal_connect(G_OBJECT(treeselection), "changed",
+                    G_CALLBACK(treeselection_changed), selparams);
+#else
     dp.ntreeitems = nselparams;
     dp.treeitems = snewn(dp.ntreeitems, GtkWidget *);
 
@@ -2071,16 +2423,38 @@ int do_config_box(const char *title, Config *cfg)
                           &selparams[index]);
         dp.treeitems[index] = selparams[index].treeitem;
     }
+#endif
 
     dp.data = cfg;
     dlg_refresh(NULL, &dp);
 
     dp.shortcuts = &selparams[0].shortcuts;
+#if !GTK_CHECK_VERSION(2,0,0)
     dp.currtreeitem = dp.treeitems[0];
+#endif
     dp.lastfocus = NULL;
     dp.retval = 0;
     dp.window = window;
 
+    {
+       /* in gtkwin.c */
+       extern void set_window_icon(GtkWidget *window,
+                                   const char *const *const *icon,
+                                   int n_icon);
+       extern const char *const *const cfg_icon[];
+       extern const int n_cfg_icon;
+       set_window_icon(window, cfg_icon, n_cfg_icon);
+    }
+
+#if !GTK_CHECK_VERSION(2,0,0)
+    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
+                                         tree);
+#endif
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
+                                  GTK_POLICY_NEVER,
+                                  GTK_POLICY_AUTOMATIC);
+    gtk_widget_show(tree);
+
     gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
     gtk_widget_show(window);
 
@@ -2114,7 +2488,6 @@ int do_config_box(const char *title, Config *cfg)
 
     gtk_main();
 
-    get_sesslist(&sl, FALSE);
     dlg_cleanup(&dp);
     sfree(selparams);
 
@@ -2188,8 +2561,7 @@ int messagebox(GtkWidget *parentwin, char *title, char *msg, int minwid, ...)
     window = gtk_dialog_new();
     gtk_window_set_title(GTK_WINDOW(window), title);
     w0 = layout_ctrls(&dp, &scs, s0, 0, GTK_WINDOW(window));
-    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->action_area),
-                      w0, TRUE, TRUE, 0);
+    set_dialog_action_area(GTK_DIALOG(window), w0);
     gtk_widget_show(w0);
     w1 = layout_ctrls(&dp, &scs, s1, 0, GTK_WINDOW(window));
     gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
@@ -2205,13 +2577,7 @@ int messagebox(GtkWidget *parentwin, char *title, char *msg, int minwid, ...)
 
     gtk_window_set_modal(GTK_WINDOW(window), TRUE);
     if (parentwin) {
-       gint x, y, w, h, dx, dy;
-       gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_NONE);
-       gdk_window_get_origin(parentwin->window, &x, &y);
-       gdk_window_get_size(parentwin->window, &w, &h);
-       dx = x + w/4;
-       dy = y + h/4;
-       gtk_widget_set_uposition(GTK_WIDGET(window), dx, dy);
+        set_transient_window_pos(parentwin, window);
        gtk_window_set_transient_for(GTK_WINDOW(window),
                                     GTK_WINDOW(parentwin));
     } else
@@ -2236,17 +2602,31 @@ static int string_width(char *text)
     GtkWidget *label = gtk_label_new(text);
     GtkRequisition req;
     gtk_widget_size_request(label, &req);
-    gtk_widget_unref(label);
+    gtk_object_sink(GTK_OBJECT(label));
     return req.width;
 }
 
-void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
-                        char *keystr, char *fingerprint)
+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"),
+                        "Yes", 'y', +1, 1,
+                        "No", 'n', -1, 0,
+                        NULL);
+    sfree(title);
+    return ret;
+}
+
+int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+                        char *keystr, char *fingerprint,
+                        void (*callback)(void *ctx, int result), void *ctx)
 {
     static const char absenttxt[] =
        "The server's host key is not cached. You have no guarantee "
        "that the server is the computer you think it is.\n"
-       "The server's key fingerprint is:\n"
+       "The server's %s key fingerprint is:\n"
        "%s\n"
        "If you trust this host, press \"Accept\" to add the key to "
        "PuTTY's cache and carry on connecting.\n"
@@ -2260,7 +2640,7 @@ void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
        "cached. This means that either the server administrator "
        "has changed the host key, or you have actually connected "
        "to another computer pretending to be the server.\n"
-       "The new key fingerprint is:\n"
+       "The new %s key fingerprint is:\n"
        "%s\n"
        "If you were expecting this change and trust the new key, "
        "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
@@ -2278,9 +2658,9 @@ void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
     ret = verify_host_key(host, port, keytype, keystr);
 
     if (ret == 0)                     /* success - key matched OK */
-       return;
+       return 1;
 
-    text = dupprintf((ret == 2 ? wrongtxt : absenttxt), fingerprint);
+    text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);
 
     ret = messagebox(GTK_WIDGET(get_window(frontend)),
                     "PuTTY Security Alert", text,
@@ -2292,29 +2672,29 @@ void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
 
     sfree(text);
 
-    if (ret == 0)
-       cleanup_exit(0);
-    else if (ret == 2)
+    if (ret == 2) {
        store_host_key(host, port, keytype, keystr);
+       return 1;                      /* continue with connection */
+    } else if (ret == 1)
+       return 1;                      /* continue with connection */
+    return 0;                         /* do not continue with connection */
 }
 
 /*
- * Ask whether the selected cipher is acceptable (since it was
+ * Ask whether the selected algorithm is acceptable (since it was
  * below the configured 'warn' threshold).
- * cs: 0 = both ways, 1 = client->server, 2 = server->client
  */
-void askcipher(void *frontend, char *ciphername, int cs)
+int askalg(void *frontend, const char *algtype, const char *algname,
+          void (*callback)(void *ctx, int result), void *ctx)
 {
     static const char msg[] =
-       "The first %scipher supported by the server is "
+       "The first %s supported by the server is "
        "%s, which is below the configured warning threshold.\n"
        "Continue with connection?";
     char *text;
     int ret;
 
-    text = dupprintf(msg, (cs == 0) ? "" :
-                     (cs == 1) ? "client-to-server " : "server-to-client ",
-                     ciphername);
+    text = dupprintf(msg, algtype, algname);
     ret = messagebox(GTK_WIDGET(get_window(frontend)),
                     "PuTTY Security Alert", text,
                     string_width("Continue with connection?"),
@@ -2324,9 +2704,9 @@ void askcipher(void *frontend, char *ciphername, int cs)
     sfree(text);
 
     if (ret) {
-       return;
+       return 1;
     } else {
-       cleanup_exit(0);
+       return 0;
     }
 }
 
@@ -2369,11 +2749,12 @@ static void licence_clicked(GtkButton *button, gpointer data)
     char *title;
 
     char *licence =
-       "Copyright 1997-2003 Simon Tatham.\n\n"
+       "Copyright 1997-2007 Simon Tatham.\n\n"
 
        "Portions copyright Robert de Bath, Joris van Rantwijk, Delian "
        "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas "
-       "Barry, Justin Bradford, Ben Harris, and CORE SDI S.A.\n\n"
+       "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, "
+       "Markus Kuhn, Colin Watson, and CORE SDI S.A.\n\n"
 
        "Permission is hereby granted, free of charge, to any person "
        "obtaining a copy of this software and associated documentation "
@@ -2406,7 +2787,7 @@ static void licence_clicked(GtkButton *button, gpointer data)
     sfree(title);
 }
 
-void about_box(void)
+void about_box(void *window)
 {
     GtkWidget *w;
     char *title;
@@ -2449,10 +2830,278 @@ void about_box(void)
                       w, FALSE, FALSE, 5);
     gtk_widget_show(w);
 
-    w = gtk_label_new("Copyright 1997-2003 Simon Tatham. All rights reserved");
+    w = gtk_label_new("Copyright 1997-2007 Simon Tatham. All rights reserved");
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox),
                       w, FALSE, FALSE, 5);
     gtk_widget_show(w);
 
+    set_transient_window_pos(GTK_WIDGET(window), aboutbox);
     gtk_widget_show(aboutbox);
 }
+
+struct eventlog_stuff {
+    GtkWidget *parentwin, *window;
+    struct controlbox *eventbox;
+    struct Shortcuts scs;
+    struct dlgparam dp;
+    union control *listctrl;
+    char **events;
+    int nevents, negsize;
+    char *seldata;
+    int sellen;
+    int ignore_selchange;
+};
+
+static void eventlog_destroy(GtkWidget *widget, gpointer data)
+{
+    struct eventlog_stuff *es = (struct eventlog_stuff *)data;
+
+    es->window = NULL;
+    sfree(es->seldata);
+    dlg_cleanup(&es->dp);
+    ctrl_free_box(es->eventbox);
+}
+static void eventlog_ok_handler(union control *ctrl, void *dlg,
+                               void *data, int event)
+{
+    if (event == EVENT_ACTION)
+       dlg_end(dlg, 0);
+}
+static void eventlog_list_handler(union control *ctrl, void *dlg,
+                                 void *data, int event)
+{
+    struct eventlog_stuff *es = (struct eventlog_stuff *)data;
+
+    if (event == EVENT_REFRESH) {
+       int i;
+
+       dlg_update_start(ctrl, dlg);
+       dlg_listbox_clear(ctrl, dlg);
+       for (i = 0; i < es->nevents; i++) {
+           dlg_listbox_add(ctrl, dlg, es->events[i]);
+       }
+       dlg_update_done(ctrl, dlg);
+    } else if (event == EVENT_SELCHANGE) {
+        int i;
+        int selsize = 0;
+
+        /*
+         * If this SELCHANGE event is happening as a result of
+         * deliberate deselection because someone else has grabbed
+         * the selection, the last thing we want to do is pre-empt
+         * them.
+         */
+        if (es->ignore_selchange)
+            return;
+
+        /*
+         * Construct the data to use as the selection.
+         */
+        sfree(es->seldata);
+        es->seldata = NULL;
+        es->sellen = 0;
+        for (i = 0; i < es->nevents; i++) {
+            if (dlg_listbox_issel(ctrl, dlg, i)) {
+                int extralen = strlen(es->events[i]);
+
+                if (es->sellen + extralen + 2 > selsize) {
+                    selsize = es->sellen + extralen + 512;
+                    es->seldata = sresize(es->seldata, selsize, char);
+                }
+
+                strcpy(es->seldata + es->sellen, es->events[i]);
+                es->sellen += extralen;
+                es->seldata[es->sellen++] = '\n';
+            }
+        }
+
+        if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY,
+                                    GDK_CURRENT_TIME)) {
+            extern GdkAtom compound_text_atom;
+
+            gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
+                                     GDK_SELECTION_TYPE_STRING, 1);
+            gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
+                                     compound_text_atom, 1);
+        }
+
+    }
+}
+
+void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata,
+                            guint info, guint time_stamp, gpointer data)
+{
+    struct eventlog_stuff *es = (struct eventlog_stuff *)data;
+
+    gtk_selection_data_set(seldata, seldata->target, 8,
+                           (unsigned char *)es->seldata, es->sellen);
+}
+
+gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
+                              gpointer data)
+{
+    struct eventlog_stuff *es = (struct eventlog_stuff *)data;
+    struct uctrl *uc;
+
+    /*
+     * Deselect everything in the list box.
+     */
+    uc = dlg_find_byctrl(&es->dp, es->listctrl);
+    es->ignore_selchange = 1;
+    gtk_list_unselect_all(GTK_LIST(uc->list));
+    es->ignore_selchange = 0;
+
+    sfree(es->seldata);
+    es->sellen = 0;
+    es->seldata = NULL;
+    return TRUE;
+}
+
+void showeventlog(void *estuff, void *parentwin)
+{
+    struct eventlog_stuff *es = (struct eventlog_stuff *)estuff;
+    GtkWidget *window, *w0, *w1;
+    GtkWidget *parent = GTK_WIDGET(parentwin);
+    struct controlset *s0, *s1;
+    union control *c;
+    int listitemheight, index;
+    char *title;
+
+    if (es->window) {
+        gtk_widget_grab_focus(es->window);
+       return;
+    }
+
+    dlg_init(&es->dp);
+
+    for (index = 0; index < lenof(es->scs.sc); index++) {
+       es->scs.sc[index].action = SHORTCUT_EMPTY;
+    }
+
+    es->eventbox = ctrl_new_box();
+
+    s0 = ctrl_getset(es->eventbox, "", "", "");
+    ctrl_columns(s0, 3, 33, 34, 33);
+    c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help),
+                       eventlog_ok_handler, P(NULL));
+    c->button.column = 1;
+    c->button.isdefault = TRUE;
+
+    s1 = ctrl_getset(es->eventbox, "x", "", "");
+    es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help),
+                                   eventlog_list_handler, P(es));
+    c->listbox.height = 10;
+    c->listbox.multisel = 2;
+    c->listbox.ncols = 3;
+    c->listbox.percentages = snewn(3, int);
+    c->listbox.percentages[0] = 25;
+    c->listbox.percentages[1] = 10;
+    c->listbox.percentages[2] = 65;
+
+    listitemheight = get_listitemheight();
+
+    es->window = window = gtk_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,
+                     listitemheight, GTK_WINDOW(window));
+    set_dialog_action_area(GTK_DIALOG(window), w0);
+    gtk_widget_show(w0);
+    w1 = layout_ctrls(&es->dp, &es->scs, s1,
+                     listitemheight, GTK_WINDOW(window));
+    gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
+    gtk_widget_set_usize(w1, 20 +
+                        string_width("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(window)->vbox),
+                      w1, TRUE, TRUE, 0);
+    gtk_widget_show(w1);
+
+    es->dp.data = es;
+    es->dp.shortcuts = &es->scs;
+    es->dp.lastfocus = NULL;
+    es->dp.retval = 0;
+    es->dp.window = window;
+
+    dlg_refresh(NULL, &es->dp);
+
+    if (parent) {
+        set_transient_window_pos(parent, window);
+       gtk_window_set_transient_for(GTK_WINDOW(window),
+                                    GTK_WINDOW(parent));
+    } else
+       gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+    gtk_widget_show(window);
+
+    gtk_signal_connect(GTK_OBJECT(window), "destroy",
+                      GTK_SIGNAL_FUNC(eventlog_destroy), es);
+    gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
+                      GTK_SIGNAL_FUNC(win_key_press), &es->dp);
+    gtk_signal_connect(GTK_OBJECT(window), "selection_get",
+                      GTK_SIGNAL_FUNC(eventlog_selection_get), es);
+    gtk_signal_connect(GTK_OBJECT(window), "selection_clear_event",
+                      GTK_SIGNAL_FUNC(eventlog_selection_clear), es);
+}
+
+void *eventlogstuff_new(void)
+{
+    struct eventlog_stuff *es;
+    es = snew(struct eventlog_stuff);
+    memset(es, 0, sizeof(*es));
+    return es;
+}
+
+void logevent_dlg(void *estuff, const char *string)
+{
+    struct eventlog_stuff *es = (struct eventlog_stuff *)estuff;
+
+    char timebuf[40];
+    struct tm tm;
+
+    if (es->nevents >= es->negsize) {
+       es->negsize += 64;
+       es->events = sresize(es->events, es->negsize, char *);
+    }
+
+    tm=ltime();
+    strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
+
+    es->events[es->nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
+    strcpy(es->events[es->nevents], timebuf);
+    strcat(es->events[es->nevents], string);
+    if (es->window) {
+       dlg_listbox_add(es->listctrl, &es->dp, es->events[es->nevents]);
+    }
+    es->nevents++;
+}
+
+int askappend(void *frontend, Filename filename,
+             void (*callback)(void *ctx, int result), void *ctx)
+{
+    static const char msgtemplate[] =
+       "The session log file \"%.*s\" already exists. "
+       "You can overwrite it with a new session log, "
+       "append your session log to the end of it, "
+       "or disable session logging for this session.";
+    char *message;
+    char *mbtitle;
+    int mbret;
+
+    message = dupprintf(msgtemplate, FILENAME_MAX, filename.path);
+    mbtitle = dupprintf("%s Log to File", appname);
+
+    mbret = messagebox(get_window(frontend), mbtitle, message,
+                      string_width("LINE OF TEXT SUITABLE FOR THE"
+                                   " ASKAPPEND WIDTH"),
+                      "Overwrite", 'o', 1, 2,
+                      "Append", 'a', 0, 1,
+                      "Disable", 'd', -1, 0,
+                      NULL);
+
+    sfree(message);
+    sfree(mbtitle);
+
+    return mbret;
+}