X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=unix%2Fgtkdlg.c;h=966164ac025e4ecf3a04bcc44802b5c907e70cfb;hb=12e019bafc75cb441e965c63e15dfceeaf71ca1e;hp=a454d8bba1f51ce34dc0695101f1156731bb2ce5;hpb=7706da5e17491b702bf8b8c0a9fb154aae0e4cbf;p=PuTTY.git diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index a454d8bb..966164ac 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -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 #include #include +#include #include #include #include @@ -21,7 +13,6 @@ #include #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; } } @@ -2355,3 +2735,373 @@ void fatalbox(char *p, ...) sfree(msg); cleanup_exit(1); } + +static GtkWidget *aboutbox = NULL; + +static void about_close_clicked(GtkButton *button, gpointer data) +{ + gtk_widget_destroy(aboutbox); + aboutbox = NULL; +} + +static void licence_clicked(GtkButton *button, gpointer data) +{ + char *title; + + char *licence = + "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, 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 " + "files (the ""Software""), to deal in the Software without restriction, " + "including without limitation the rights to use, copy, modify, merge, " + "publish, distribute, sublicense, and/or sell copies of the Software, " + "and to permit persons to whom the Software is furnished to do so, " + "subject to the following conditions:\n\n" + + "The above copyright notice and this permission notice shall be " + "included in all copies or substantial portions of the Software.\n\n" + + "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT " + "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, " + "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF " + "MERCHANTABILITY, FITNESS FOR A PARTICULAR " + "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE " + "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES " + "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, " + "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN " + "CONNECTION WITH THE SOFTWARE OR THE USE OR " + "OTHER DEALINGS IN THE SOFTWARE."; + + title = dupcat(appname, " Licence", NULL); + assert(aboutbox != NULL); + 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); + sfree(title); +} + +void about_box(void *window) +{ + GtkWidget *w; + char *title; + + if (aboutbox) { + gtk_widget_grab_focus(aboutbox); + return; + } + + aboutbox = gtk_dialog_new(); + gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10); + title = dupcat("About ", appname, NULL); + gtk_window_set_title(GTK_WINDOW(aboutbox), title); + sfree(title); + + w = gtk_button_new_with_label("Close"); + GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); + gtk_window_set_default(GTK_WINDOW(aboutbox), w); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(aboutbox)->action_area), + w, FALSE, FALSE, 0); + gtk_signal_connect(GTK_OBJECT(w), "clicked", + GTK_SIGNAL_FUNC(about_close_clicked), NULL); + gtk_widget_show(w); + + w = gtk_button_new_with_label("View Licence"); + GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(aboutbox)->action_area), + w, FALSE, FALSE, 0); + gtk_signal_connect(GTK_OBJECT(w), "clicked", + GTK_SIGNAL_FUNC(licence_clicked), NULL); + gtk_widget_show(w); + + w = gtk_label_new(appname); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox), + w, FALSE, FALSE, 0); + gtk_widget_show(w); + + w = gtk_label_new(ver); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox), + w, FALSE, FALSE, 5); + gtk_widget_show(w); + + 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; +}