/*
 * Modifications Copyright 1996, 1999, 2000, 2001, 2002 by Paul Mattes.
 * Copyright October 1995 by Dick Altenbern.
 * Based in part on code Copyright 1993, 1994, 1995 by Paul Mattes.
 *  Permission to use, copy, modify, and distribute this software and its
 *  documentation for any purpose and without fee is hereby granted,
 *  provided that the above copyright notice appear in all copies and that
 *  both that copyright notice and this permission notice appear in
 *  supporting documentation.
 *
 * x3270, c3270, s3270 and tcl3270 are distributed in the hope that they will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the file LICENSE
 * for more details.
 */

/*
 *	ft.c
 *		This module handles the file transfer dialogs.
 */

#include "globals.h"

#if defined(X3270_FT) /*[*/

#if defined(X3270_DISPLAY) /*[*/
#include <X11/StringDefs.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Form.h>
#include <X11/Shell.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/TextSrc.h>
#include <X11/Xaw/TextSink.h>
#include <X11/Xaw/AsciiSrc.h>
#include <X11/Xaw/AsciiSink.h>
#endif /*]*/
#include <errno.h>

#include "appres.h"
#include "actionsc.h"
#include "ft_cutc.h"
#include "ft_dftc.h"
#include "ftc.h"
#include "dialogc.h"
#include "hostc.h"
#include "kybdc.h"
#include "macrosc.h"
#include "objects.h"
#include "popupsc.h"
#include "telnetc.h"
#include "utilc.h"

/* Macros. */
#define eos(s)	strchr((s), '\0')

#if defined(X3270_DISPLAY) /*[*/
#define FILE_WIDTH	300	/* width of file name widgets */
#define MARGIN		3	/* distance from margins to widgets */
#define CLOSE_VGAP	0	/* distance between paired toggles */
#define FAR_VGAP	10	/* distance between single toggles and groups */
#define BUTTON_GAP	5	/* horizontal distance between buttons */
#define COLUMN_GAP	40	/* distance between columns */
#endif /*]*/

#define BN	(Boolean *)NULL

/* Externals. */
#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
extern Pixmap diamond;
extern Pixmap no_diamond;
extern Pixmap null;
extern Pixmap dot;
extern Pixmap no_dot;
#endif /*]*/

/* Globals. */
enum ft_state ft_state = FT_NONE;	/* File transfer state */
char *ft_local_filename;		/* Local file to transfer to/from */
FILE *ft_local_file = (FILE *)NULL;	/* File descriptor for local file */
Boolean ft_last_cr = False;		/* CR was last char in local file */
Boolean ascii_flag = True;		/* Convert to ascii */
Boolean cr_flag = True;			/* Add crlf to each line */
Boolean remap_flag = True;		/* Remap ASCII<->EBCDIC */
unsigned long ft_length = 0;		/* Length of transfer */

/* Statics. */
#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
static Widget ft_dialog, ft_shell, local_file, host_file;
static Widget lrecl_widget, blksize_widget;
static Widget primspace_widget, secspace_widget;
static Widget send_toggle, receive_toggle;
static Widget vm_toggle, tso_toggle;
static Widget ascii_toggle, binary_toggle;
static Widget cr_widget;
static Widget remap_widget;
#endif /*]*/

static char *ft_host_filename;		/* Host file to transfer to/from */
static Boolean receive_flag = True;	/* Current transfer is receive */
static Boolean append_flag = False;	/* Append transfer */
static Boolean vm_flag = False;		/* VM Transfer flag */

#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
static Widget recfm_options[5];
static Widget units_options[5];
static struct toggle_list recfm_toggles = { recfm_options };
static struct toggle_list units_toggles = { units_options };
#endif /*]*/

static enum recfm {
	DEFAULT_RECFM, FIXED, VARIABLE, UNDEFINED
} recfm = DEFAULT_RECFM;
#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
static Boolean recfm_default = True;
static enum recfm r_default_recfm = DEFAULT_RECFM;
static enum recfm r_fixed = FIXED;
static enum recfm r_variable = VARIABLE;
static enum recfm r_undefined = UNDEFINED;
#endif /*]*/

static enum units {
	DEFAULT_UNITS, TRACKS, CYLINDERS, AVBLOCK
} units = DEFAULT_UNITS;
#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
static Boolean units_default = True;
static enum units u_default_units = DEFAULT_UNITS;
static enum units u_tracks = TRACKS;
static enum units u_cylinders = CYLINDERS;
static enum units u_avblock = AVBLOCK;
#endif /*]*/

static Boolean allow_overwrite = False;
#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
static sr_t *ft_sr = (sr_t *)NULL;

static Widget progress_shell, from_file, to_file;
static Widget ft_status, waiting, aborting;
static String status_string;
#endif /*]*/
static struct timeval t0;		/* Starting time */
static Boolean ft_is_cut;		/* File transfer is CUT-style */

#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
static Widget overwrite_shell;
#endif /*]*/
static Boolean ft_is_action;

#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
static void ft_cancel(Widget w, XtPointer client_data, XtPointer call_data);
static void ft_popup_callback(Widget w, XtPointer client_data,
    XtPointer call_data);
static void ft_popup_init(void);
static int ft_start(void);
static void ft_start_callback(Widget w, XtPointer call_parms,
    XtPointer call_data);
static void overwrite_cancel_callback(Widget w, XtPointer client_data,
    XtPointer call_data);
static void overwrite_okay_callback(Widget w, XtPointer client_data,
    XtPointer call_data);
static void overwrite_popdown(Widget w, XtPointer client_data,
    XtPointer call_data);
static void overwrite_popup_init(void);
static void popup_overwrite(void);
static void popup_progress(void);
static void progress_cancel_callback(Widget w, XtPointer client_data,
    XtPointer call_data);
static void progress_popup_callback(Widget w, XtPointer client_data,
    XtPointer call_data);
static void progress_popup_init(void);
static void recfm_callback(Widget w, XtPointer user_data, XtPointer call_data);
static void toggle_append(Widget w, XtPointer client_data, XtPointer call_data);
static void toggle_ascii(Widget w, XtPointer client_data, XtPointer call_data);
static void toggle_cr(Widget w, XtPointer client_data, XtPointer call_data);
static void toggle_remap(Widget w, XtPointer client_data, XtPointer call_data);
static void toggle_receive(Widget w, XtPointer client_data,
    XtPointer call_data);
static void toggle_vm(Widget w, XtPointer client_data, XtPointer call_data);
static void units_callback(Widget w, XtPointer user_data, XtPointer call_data);
#endif /*]*/
static void ft_connected(Boolean ignored);
static void ft_in3270(Boolean ignored);

/* Main external entry point. */

#if !defined(X3270_DISPLAY) || !defined(X3270_MENUS) /*[*/
void
ft_init(void)
{
	/* Register for state changes. */
	register_schange(ST_CONNECT, ft_connected);
	register_schange(ST_3270_MODE, ft_in3270);
}
#endif /*]*/

#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
/* "File Transfer" dialog. */

/*
 * Pop up the "Transfer" menu.
 * Called back from the "File Transfer" option on the File menu.
 */
void
popup_ft(Widget w unused, XtPointer call_parms unused,
	XtPointer call_data unused)
{
	/* Initialize it. */
	if (ft_shell == (Widget)NULL)
		ft_popup_init();

	/* Pop it up. */
	dialog_set(&ft_sr, ft_dialog);
	popup_popup(ft_shell, XtGrabNone);
}

/* Initialize the transfer pop-up. */
static void
ft_popup_init(void)
{
	Widget w;
	Widget cancel_button;
	Widget local_label, host_label;
	Widget append_widget;
	Widget lrecl_label, blksize_label, primspace_label, secspace_label;
	Widget h_ref = (Widget)NULL;
	Dimension d1;
	Dimension maxw = 0;
	Widget recfm_label, units_label;
	Widget start_button;

	/* Register for state changes. */
	register_schange(ST_CONNECT, ft_connected);
	register_schange(ST_3270_MODE, ft_in3270);

	/* Prep the dialog functions. */
	dialog_set(&ft_sr, ft_dialog);

	/* Create the menu shell. */
	ft_shell = XtVaCreatePopupShell(
	    "ftPopup", transientShellWidgetClass, toplevel,
	    NULL);
	XtAddCallback(ft_shell, XtNpopupCallback, place_popup,
	    (XtPointer)CenterP);
	XtAddCallback(ft_shell, XtNpopupCallback, ft_popup_callback,
	    (XtPointer)NULL);

	/* Create the form within the shell. */
	ft_dialog = XtVaCreateManagedWidget(
	    ObjDialog, formWidgetClass, ft_shell,
	    NULL);

	/* Create the file name widgets. */
	local_label = XtVaCreateManagedWidget(
	    "local", labelWidgetClass, ft_dialog,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	local_file = XtVaCreateManagedWidget(
	    "value", asciiTextWidgetClass, ft_dialog,
	    XtNeditType, XawtextEdit,
	    XtNwidth, FILE_WIDTH,
	    XtNvertDistance, FAR_VGAP,
	    XtNfromHoriz, local_label,
	    XtNhorizDistance, 0,
	    NULL);
	dialog_match_dimension(local_label, local_file, XtNheight);
	w = XawTextGetSource(local_file);
	if (w == NULL)
		XtWarning("Cannot find text source in dialog");
	else
		XtAddCallback(w, XtNcallback, dialog_text_callback,
		    (XtPointer)&t_unixfile);
	dialog_register_sensitivity(local_file,
	    BN, False,
	    BN, False,
	    BN, False);

	host_label = XtVaCreateManagedWidget(
	    "host", labelWidgetClass, ft_dialog,
	    XtNfromVert, local_label,
	    XtNvertDistance, 3,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	host_file = XtVaCreateManagedWidget(
	    "value", asciiTextWidgetClass, ft_dialog,
	    XtNeditType, XawtextEdit,
	    XtNwidth, FILE_WIDTH,
	    XtNdisplayCaret, False,
	    XtNfromVert, local_label,
	    XtNvertDistance, 3,
	    XtNfromHoriz, host_label,
	    XtNhorizDistance, 0,
	    NULL);
	dialog_match_dimension(host_label, host_file, XtNheight);
	dialog_match_dimension(local_label, host_label, XtNwidth);
	w = XawTextGetSource(host_file);
	if (w == NULL)
		XtWarning("Cannot find text source in dialog");
	else
		XtAddCallback(w, XtNcallback, dialog_text_callback,
		    (XtPointer)&t_hostfile);
	dialog_register_sensitivity(host_file,
	    BN, False,
	    BN, False,
	    BN, False);

	/* Create the left column. */

	/* Create send/receive toggles. */
	send_toggle = XtVaCreateManagedWidget(
	    "send", commandWidgetClass, ft_dialog,
	    XtNfromVert, host_label,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(send_toggle, receive_flag ? no_diamond : diamond);
	XtAddCallback(send_toggle, XtNcallback, toggle_receive,
	    (XtPointer)&s_false);
	receive_toggle = XtVaCreateManagedWidget(
	    "receive", commandWidgetClass, ft_dialog,
	    XtNfromVert, send_toggle,
	    XtNvertDistance, CLOSE_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(receive_toggle, receive_flag ? diamond : no_diamond);
	XtAddCallback(receive_toggle, XtNcallback, toggle_receive,
	    (XtPointer)&s_true);

	/* Create ASCII/binary toggles. */
	ascii_toggle = XtVaCreateManagedWidget(
	    "ascii", commandWidgetClass, ft_dialog,
	    XtNfromVert, receive_toggle,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(ascii_toggle, ascii_flag ? diamond : no_diamond);
	XtAddCallback(ascii_toggle, XtNcallback, toggle_ascii,
	    (XtPointer)&s_true);
	binary_toggle = XtVaCreateManagedWidget(
	    "binary", commandWidgetClass, ft_dialog,
	    XtNfromVert, ascii_toggle,
	    XtNvertDistance, CLOSE_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(binary_toggle, ascii_flag ? no_diamond : diamond);
	XtAddCallback(binary_toggle, XtNcallback, toggle_ascii,
	    (XtPointer)&s_false);

	/* Create append toggle. */
	append_widget = XtVaCreateManagedWidget(
	    "append", commandWidgetClass, ft_dialog,
	    XtNfromVert, binary_toggle,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(append_widget, append_flag ? dot : no_dot);
	XtAddCallback(append_widget, XtNcallback, toggle_append, NULL);

	/* Set up the recfm group. */
	recfm_label = XtVaCreateManagedWidget(
	    "file", labelWidgetClass, ft_dialog,
	    XtNfromVert, append_widget,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_register_sensitivity(recfm_label,
	    &receive_flag, False,
	    BN, False,
	    BN, False);

	recfm_options[0] = XtVaCreateManagedWidget(
	    "recfmDefault", commandWidgetClass, ft_dialog,
	    XtNfromVert, recfm_label,
	    XtNvertDistance, 3,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(recfm_options[0],
	    (recfm == DEFAULT_RECFM) ? diamond : no_diamond);
	XtAddCallback(recfm_options[0], XtNcallback, recfm_callback,
	    (XtPointer)&r_default_recfm);
	dialog_register_sensitivity(recfm_options[0],
	    &receive_flag, False,
	    BN, False,
	    BN, False);

	recfm_options[1] = XtVaCreateManagedWidget(
	    "fixed", commandWidgetClass, ft_dialog,
	    XtNfromVert, recfm_options[0],
	    XtNvertDistance, CLOSE_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(recfm_options[1],
	    (recfm == FIXED) ? diamond : no_diamond);
	XtAddCallback(recfm_options[1], XtNcallback, recfm_callback,
	    (XtPointer)&r_fixed);
	dialog_register_sensitivity(recfm_options[1],
	    &receive_flag, False,
	    BN, False,
	    BN, False);

	recfm_options[2] = XtVaCreateManagedWidget(
	    "variable", commandWidgetClass, ft_dialog,
	    XtNfromVert, recfm_options[1],
	    XtNvertDistance, CLOSE_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(recfm_options[2],
	    (recfm == VARIABLE) ? diamond : no_diamond);
	XtAddCallback(recfm_options[2], XtNcallback, recfm_callback,
	    (XtPointer)&r_variable);
	dialog_register_sensitivity(recfm_options[2],
	    &receive_flag, False,
	    BN, False,
	    BN, False);

	recfm_options[3] = XtVaCreateManagedWidget(
	    "undefined", commandWidgetClass, ft_dialog,
	    XtNfromVert, recfm_options[2],
	    XtNvertDistance, CLOSE_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(recfm_options[3],
	    (recfm == UNDEFINED) ? diamond : no_diamond);
	XtAddCallback(recfm_options[3], XtNcallback, recfm_callback,
	    (XtPointer)&r_undefined);
	dialog_register_sensitivity(recfm_options[3],
	    &receive_flag, False,
	    &vm_flag, False,
	    BN, False);

	lrecl_label = XtVaCreateManagedWidget(
	    "lrecl", labelWidgetClass, ft_dialog,
	    XtNfromVert, recfm_options[3],
	    XtNvertDistance, 3,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_register_sensitivity(lrecl_label,
	    &receive_flag, False,
	    &recfm_default, False,
	    BN, False);
	lrecl_widget = XtVaCreateManagedWidget(
	    "value", asciiTextWidgetClass, ft_dialog,
	    XtNfromVert, recfm_options[3],
	    XtNvertDistance, 3,
	    XtNfromHoriz, lrecl_label,
	    XtNhorizDistance, MARGIN,
	    XtNwidth, 100,
	    XtNeditType, XawtextEdit,
	    XtNdisplayCaret, False,
	    NULL);
	dialog_match_dimension(lrecl_label, lrecl_widget, XtNheight);
	w = XawTextGetSource(lrecl_widget);
	if (w == NULL)
		XtWarning("Cannot find text source in dialog");
	else
		XtAddCallback(w, XtNcallback, dialog_text_callback,
		    (XtPointer)&t_numeric);
	dialog_register_sensitivity(lrecl_widget,
	    &receive_flag, False,
	    &recfm_default, False,
	    BN, False);

	blksize_label = XtVaCreateManagedWidget(
	    "blksize", labelWidgetClass, ft_dialog,
	    XtNfromVert, lrecl_widget,
	    XtNvertDistance, 3,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	dialog_match_dimension(blksize_label, lrecl_label, XtNwidth);
	dialog_register_sensitivity(blksize_label,
	    &receive_flag, False,
	    &recfm_default, False,
	    BN, False);
	blksize_widget = XtVaCreateManagedWidget(
	    "value", asciiTextWidgetClass, ft_dialog,
	    XtNfromVert, lrecl_widget,
	    XtNvertDistance, 3,
	    XtNfromHoriz, blksize_label,
	    XtNhorizDistance, MARGIN,
	    XtNwidth, 100,
	    XtNeditType, XawtextEdit,
	    XtNdisplayCaret, False,
	    NULL);
	dialog_match_dimension(blksize_label, blksize_widget, XtNheight);
	w = XawTextGetSource(blksize_widget);
	if (w == NULL)
		XtWarning("Cannot find text source in dialog");
	else
		XtAddCallback(w, XtNcallback, dialog_text_callback,
		    (XtPointer)&t_numeric);
	dialog_register_sensitivity(blksize_widget,
	    &receive_flag, False,
	    &recfm_default, False,
	    BN, False);


	/* Find the widest widget in the left column. */
	XtVaGetValues(send_toggle, XtNwidth, &maxw, NULL);
	h_ref = send_toggle;
#define REMAX(w) { \
		XtVaGetValues((w), XtNwidth, &d1, NULL); \
		if (d1 > maxw) { \
			maxw = d1; \
			h_ref = (w); \
		} \
	}
	REMAX(receive_toggle);
	REMAX(ascii_toggle);
	REMAX(binary_toggle);
	REMAX(append_widget);
#undef REMAX

	/* Create the right column buttons. */

	/* Create VM/TSO toggle. */
	vm_toggle = XtVaCreateManagedWidget(
	    "vm", commandWidgetClass, ft_dialog,
	    XtNfromVert, host_label,
	    XtNvertDistance, FAR_VGAP,
	    XtNfromHoriz, h_ref,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(vm_toggle, vm_flag ? diamond : no_diamond);
	XtAddCallback(vm_toggle, XtNcallback, toggle_vm, (XtPointer)&s_true);
	tso_toggle =  XtVaCreateManagedWidget(
	    "tso", commandWidgetClass, ft_dialog,
	    XtNfromVert, vm_toggle,
	    XtNvertDistance, CLOSE_VGAP,
	    XtNfromHoriz, h_ref,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(tso_toggle, vm_flag ? no_diamond : diamond);
	XtAddCallback(tso_toggle, XtNcallback, toggle_vm, (XtPointer)&s_false);

	/* Create CR toggle. */
	cr_widget = XtVaCreateManagedWidget(
	    "cr", commandWidgetClass, ft_dialog,
	    XtNfromVert, tso_toggle,
	    XtNvertDistance, FAR_VGAP,
	    XtNfromHoriz, h_ref,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(cr_widget, cr_flag ? dot : no_dot);
	XtAddCallback(cr_widget, XtNcallback, toggle_cr, 0);
	dialog_register_sensitivity(cr_widget,
	    BN, False,
	    BN, False,
	    BN, False);

	/* Create remap toggle. */
	remap_widget = XtVaCreateManagedWidget(
	    "remap", commandWidgetClass, ft_dialog,
	    XtNfromVert, cr_widget,
	    XtNfromHoriz, h_ref,
	    XtNvertDistance, CLOSE_VGAP,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(remap_widget, remap_flag ? dot : no_dot);
	XtAddCallback(remap_widget, XtNcallback, toggle_remap, NULL);
	dialog_register_sensitivity(remap_widget,
	    &ascii_flag, True,
	    BN, False,
	    BN, False);

	/* Set up the Units group. */
	units_label = XtVaCreateManagedWidget(
	    "units", labelWidgetClass, ft_dialog,
	    XtNfromVert, append_widget,
	    XtNvertDistance, FAR_VGAP,
	    XtNfromHoriz, h_ref,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_register_sensitivity(units_label,
	    &receive_flag, False,
	    &vm_flag, False,
	    BN, False);

	units_options[0] = XtVaCreateManagedWidget(
	    "spaceDefault", commandWidgetClass, ft_dialog,
	    XtNfromVert, units_label,
	    XtNvertDistance, 3,
	    XtNfromHoriz, h_ref,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(units_options[0],
	    (units == DEFAULT_UNITS) ? diamond : no_diamond);
	XtAddCallback(units_options[0], XtNcallback,
	    units_callback, (XtPointer)&u_default_units);
	dialog_register_sensitivity(units_options[0],
	    &receive_flag, False,
	    &vm_flag, False,
	    BN, False);

	units_options[1] = XtVaCreateManagedWidget(
	    "tracks", commandWidgetClass, ft_dialog,
	    XtNfromVert, units_options[0],
	    XtNvertDistance, CLOSE_VGAP,
	    XtNfromHoriz, h_ref,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(units_options[1],
	    (units == TRACKS) ? diamond : no_diamond);
	XtAddCallback(units_options[1], XtNcallback,
	    units_callback, (XtPointer)&u_tracks);
	dialog_register_sensitivity(units_options[1],
	    &receive_flag, False,
	    &vm_flag, False,
	    BN, False);

	units_options[2] = XtVaCreateManagedWidget(
	    "cylinders", commandWidgetClass, ft_dialog,
	    XtNfromVert, units_options[1],
	    XtNvertDistance, CLOSE_VGAP,
	    XtNfromHoriz, h_ref,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(units_options[2],
	    (units == CYLINDERS) ? diamond : no_diamond);
	XtAddCallback(units_options[2], XtNcallback,
	    units_callback, (XtPointer)&u_cylinders);
	dialog_register_sensitivity(units_options[2],
	    &receive_flag, False,
	    &vm_flag, False,
	    BN, False);

	units_options[3] = XtVaCreateManagedWidget(
	    "avblock", commandWidgetClass, ft_dialog,
	    XtNfromVert, units_options[2],
	    XtNvertDistance, CLOSE_VGAP,
	    XtNfromHoriz, h_ref,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_apply_bitmap(units_options[3],
	    (units == AVBLOCK) ? diamond : no_diamond);
	XtAddCallback(units_options[3], XtNcallback,
	    units_callback, (XtPointer)&u_avblock);
	dialog_register_sensitivity(units_options[3],
	    &receive_flag, False,
	    &vm_flag, False,
	    BN, False);

	primspace_label = XtVaCreateManagedWidget(
	    "primspace", labelWidgetClass, ft_dialog,
	    XtNfromVert, units_options[3],
	    XtNvertDistance, 3,
	    XtNfromHoriz, h_ref,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_register_sensitivity(primspace_label,
	    &receive_flag, False,
	    &vm_flag, False,
	    &units_default, False);
	primspace_widget = XtVaCreateManagedWidget(
	    "value", asciiTextWidgetClass, ft_dialog,
	    XtNfromVert, units_options[3],
	    XtNvertDistance, 3,
	    XtNfromHoriz, primspace_label,
	    XtNhorizDistance, 0,
	    XtNwidth, 100,
	    XtNeditType, XawtextEdit,
	    XtNdisplayCaret, False,
	    NULL);
	dialog_match_dimension(primspace_label, primspace_widget, XtNheight);
	w = XawTextGetSource(primspace_widget);
	if (w == NULL)
		XtWarning("Cannot find text source in dialog");
	else
		XtAddCallback(w, XtNcallback, dialog_text_callback,
		    (XtPointer)&t_numeric);
	dialog_register_sensitivity(primspace_widget,
	    &receive_flag, False,
	    &vm_flag, False,
	    &units_default, False);

	secspace_label = XtVaCreateManagedWidget(
	    "secspace", labelWidgetClass, ft_dialog,
	    XtNfromVert, primspace_widget,
	    XtNvertDistance, 3,
	    XtNfromHoriz, h_ref,
	    XtNhorizDistance, COLUMN_GAP,
	    XtNborderWidth, 0,
	    NULL);
	dialog_match_dimension(primspace_label, secspace_label, XtNwidth);
	dialog_register_sensitivity(secspace_label,
	    &receive_flag, False,
	    &vm_flag, False,
	    &units_default, False);
	secspace_widget = XtVaCreateManagedWidget(
	    "value", asciiTextWidgetClass, ft_dialog,
	    XtNfromVert, primspace_widget,
	    XtNvertDistance, 3,
	    XtNfromHoriz, secspace_label,
	    XtNhorizDistance, 0,
	    XtNwidth, 100,
	    XtNeditType, XawtextEdit,
	    XtNdisplayCaret, False,
	    NULL);
	dialog_match_dimension(secspace_label, secspace_widget, XtNheight);
	w = XawTextGetSource(secspace_widget);
	if (w == NULL)
		XtWarning("Cannot find text source in dialog");
	else
		XtAddCallback(w, XtNcallback, dialog_text_callback,
		    (XtPointer)&t_numeric);
	dialog_register_sensitivity(secspace_widget,
	    &receive_flag, False,
	    &vm_flag, False,
	    &units_default, False);

	/* Set up the buttons at the bottom. */
	start_button = XtVaCreateManagedWidget(
	    ObjConfirmButton, commandWidgetClass, ft_dialog,
	    XtNfromVert, blksize_label,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    NULL);
	XtAddCallback(start_button, XtNcallback, ft_start_callback,
	    (XtPointer)NULL);

	cancel_button = XtVaCreateManagedWidget(
	    ObjCancelButton, commandWidgetClass, ft_dialog,
	    XtNfromVert, blksize_label,
	    XtNvertDistance, FAR_VGAP,
	    XtNfromHoriz, start_button,
	    XtNhorizDistance, BUTTON_GAP,
	    NULL);
	XtAddCallback(cancel_button, XtNcallback, ft_cancel, 0);
}

/* Callbacks for all the transfer widgets. */

/* Transfer pop-up popping up. */
static void
ft_popup_callback(Widget w unused, XtPointer client_data unused,
	XtPointer call_data unused)
{
	/* Set the focus to the local file widget. */
	PA_dialog_focus_action(local_file, (XEvent *)NULL, (String *)NULL,
	    (Cardinal *)NULL);

	/* Disallow overwrites. */
	allow_overwrite = False;
}

/* Cancel button pushed. */
static void
ft_cancel(Widget w unused, XtPointer client_data unused,
	XtPointer call_data unused)
{
	XtPopdown(ft_shell);
}

/* recfm options. */
static void
recfm_callback(Widget w, XtPointer user_data, XtPointer call_data unused)
{
	recfm = *(enum recfm *)user_data;
	recfm_default = (recfm == DEFAULT_RECFM);
	dialog_check_sensitivity(&recfm_default);
	dialog_flip_toggles(&recfm_toggles, w);
}

/* Units options. */
static void
units_callback(Widget w, XtPointer user_data, XtPointer call_data unused)
{
	units = *(enum units *)user_data;
	units_default = (units == DEFAULT_UNITS);
	dialog_check_sensitivity(&units_default);
	dialog_flip_toggles(&units_toggles, w);
}

/* OK button pushed. */
static void
ft_start_callback(Widget w unused, XtPointer call_parms unused,
	XtPointer call_data unused)
{
	if (ft_start()) {
		XtPopdown(ft_shell);
		popup_progress();
	}
}

/* Send/receive options. */
static void
toggle_receive(Widget w unused, XtPointer client_data,
	XtPointer call_data unused)
{
	/* Toggle the flag */
	receive_flag = *(Boolean *)client_data;

	/* Change the widget states. */
	dialog_mark_toggle(receive_toggle, receive_flag ? diamond : no_diamond);
	dialog_mark_toggle(send_toggle, receive_flag ? no_diamond : diamond);
	dialog_check_sensitivity(&receive_flag);
}

/* Ascii/binary options. */
static void
toggle_ascii(Widget w unused, XtPointer client_data, XtPointer call_data unused)
{
	/* Toggle the flag. */
	ascii_flag = *(Boolean *)client_data;

	/* Change the widget states. */
	dialog_mark_toggle(ascii_toggle, ascii_flag ? diamond : no_diamond);
	dialog_mark_toggle(binary_toggle, ascii_flag ? no_diamond : diamond);
	cr_flag = ascii_flag;
	remap_flag = ascii_flag;
	dialog_mark_toggle(cr_widget, cr_flag ? dot : no_dot);
	dialog_mark_toggle(remap_widget, remap_flag ? dot : no_dot);
	dialog_check_sensitivity(&ascii_flag);
}

/* CR option. */
static void
toggle_cr(Widget w, XtPointer client_data unused, XtPointer call_data unused)
{
	/* Toggle the cr flag */
	cr_flag = !cr_flag;

	dialog_mark_toggle(w, cr_flag ? dot : no_dot);
}

/* Append option. */
static void
toggle_append(Widget w, XtPointer client_data unused,
	XtPointer call_data unused)
{
	/* Toggle Append Flag */
	append_flag = !append_flag;

	dialog_mark_toggle(w, append_flag ? dot : no_dot);
}

/* Remap option. */
static void
toggle_remap(Widget w, XtPointer client_data unused,
	XtPointer call_data unused)
{
	/* Toggle Remap Flag */
	remap_flag = !remap_flag;

	dialog_mark_toggle(w, remap_flag ? dot : no_dot);
}

/* TSO/VM option. */
static void
toggle_vm(Widget w unused, XtPointer client_data unused,
    XtPointer call_data unused)
{
	/* Toggle the flag. */
	vm_flag = *(Boolean *)client_data;

	/* Change the widget states. */
	dialog_mark_toggle(vm_toggle, vm_flag ? diamond : no_diamond);
	dialog_mark_toggle(tso_toggle, vm_flag ? no_diamond : diamond);

	if (vm_flag) {
		if (recfm == UNDEFINED) {
			recfm = DEFAULT_RECFM;
			recfm_default = True;
			dialog_flip_toggles(&recfm_toggles,
			    recfm_toggles.widgets[0]);
		}
	}
	dialog_check_sensitivity(&vm_flag);
}

/*
 * Begin the transfer.
 * Returns 1 if the transfer has started, 0 otherwise.
 */
static int
ft_start(void)
{
	char opts[80];
	char *op = opts + 1;
	char *cmd;
	String lrecl, blksize, primspace, secspace;
	unsigned flen;

	ft_is_action = False;

	/* Get the host file from its widget */
	XtVaGetValues(host_file, XtNstring, &ft_host_filename, NULL);
	if (!*ft_host_filename)
		return 0;
	/* XXX: probably more validation to do here */

	/* Get the local file from it widget */
	XtVaGetValues(local_file, XtNstring,  &ft_local_filename, NULL);
	if (!*ft_local_filename)
		return 0;

	/* See if the local file can be overwritten. */
	if (receive_flag && !append_flag && !allow_overwrite) {
		ft_local_file = fopen(ft_local_filename, "r");
		if (ft_local_file != (FILE *)NULL) {
			(void) fclose(ft_local_file);
			ft_local_file = (FILE *)NULL;
			popup_overwrite();
			return 0;
		}
	}

	/* Open the local file. */
	ft_local_file = fopen(ft_local_filename,
	    receive_flag ?
		(append_flag ? "a" : "w" ) :
		"r");
	if (ft_local_file == (FILE *)NULL) {
		allow_overwrite = False;
		popup_an_errno(errno, "Open(%s)", ft_local_filename);
		return 0;
	}

	/* Build the ind$file command */
	op[0] = '\0';
	if (ascii_flag)
		strcat(op, " ascii");
	if (cr_flag)
		strcat(op, " crlf");
	if (append_flag && !receive_flag)
		strcat(op, " append");
	if (!receive_flag) {
		if (!vm_flag) {
			if (recfm != DEFAULT_RECFM) {
				/* RECFM Entered, process */
				strcat(op, " recfm(");
				switch (recfm) {
				    case FIXED:
					strcat(op, "f");
					break;
				    case VARIABLE:
					strcat(op, "v");
					break;
				    case UNDEFINED:
					strcat(op, "u");
					break;
				    default:
					break;
				};
				strcat(op, ")");
				XtVaGetValues(lrecl_widget,
				    XtNstring, &lrecl,
				    NULL);
				if (strlen(lrecl) > 0)
					sprintf(eos(op), " lrecl(%s)", lrecl);
				XtVaGetValues(blksize_widget,
				    XtNstring, &blksize,
				    NULL);
				if (strlen(blksize) > 0)
					sprintf(eos(op), " blksize(%s)",
					    blksize);
			}
			if (units != DEFAULT_UNITS) {
				/* Space Entered, processs it */
				switch (units) {
				    case TRACKS:
					strcat(op, " tracks");
					break;
				    case CYLINDERS:
					strcat(op, " cylinders");
					break;
				    case AVBLOCK:
					strcat(op, " avblock");
					break;
				    default:
					break;
				};
				XtVaGetValues(primspace_widget, XtNstring,
				    &primspace, NULL);
				if (strlen(primspace) > 0) {
					sprintf(eos(op), " space(%s",
					    primspace);
					XtVaGetValues(secspace_widget,
					    XtNstring, &secspace,
					    NULL);
					if (strlen(secspace) > 0)
						sprintf(eos(op), ",%s",
						    secspace);
					strcat(op, ")");
				}
			}
		} else {
			if (recfm != DEFAULT_RECFM) {
				strcat(op, " recfm ");
				switch (recfm) {
				    case FIXED:
					strcat(op, "f");
					break;
				    case VARIABLE:
					strcat(op, "v");
					break;
				    default:
					break;
				};

				XtVaGetValues(lrecl_widget,
				    XtNstring, &lrecl,
				    NULL);
				if (strlen(lrecl) > 0)
					sprintf(eos(op), " lrecl %s", lrecl);
			}
		}
	}

	/* Insert the '(' for VM options. */
	if (strlen(op) > 0 && vm_flag) {
		opts[0] = ' ';
		opts[1] = '(';
		op = opts;
	}

	/* Build the whole command. */
	cmd = xs_buffer("%s %s %s%s\\n",
	    appres.ft_command,
	    receive_flag ? "get" : "put", ft_host_filename, op);

	/* Erase the line and enter the command. */
	flen = kybd_prime();
	if (!flen || flen < strlen(cmd) - 1) {
		XtFree(cmd);
		popup_an_error(get_message("ftUnable"));
		allow_overwrite = False;
		return 0;
	}
	(void) emulate_input(cmd, strlen(cmd), False);
	XtFree(cmd);

	/* Get this thing started. */
	ft_state = FT_AWAIT_ACK;
	ft_is_cut = False;
	ft_last_cr = False;

	return 1;
}

/* "Transfer in Progress" pop-up. */

/* Pop up the "in progress" pop-up. */
static void
popup_progress(void)
{
	/* Initialize it. */
	if (progress_shell == (Widget)NULL)
		progress_popup_init();

	/* Pop it up. */
	popup_popup(progress_shell, XtGrabNone);
}

/* Initialize the "in progress" pop-up. */
static void
progress_popup_init(void)
{
	Widget progress_pop, from_label, to_label, cancel_button;

	/* Create the shell. */
	progress_shell = XtVaCreatePopupShell(
	    "ftProgressPopup", transientShellWidgetClass, toplevel,
	    NULL);
	XtAddCallback(progress_shell, XtNpopupCallback, place_popup,
	    (XtPointer)CenterP);
	XtAddCallback(progress_shell, XtNpopupCallback,
	    progress_popup_callback, (XtPointer)NULL);

	/* Create a form structure to contain the other stuff */
	progress_pop = XtVaCreateManagedWidget(
	    ObjDialog, formWidgetClass, progress_shell,
	    NULL);

	/* Create the widgets. */
	from_label = XtVaCreateManagedWidget(
	    "fromLabel", labelWidgetClass, progress_pop,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	from_file = XtVaCreateManagedWidget(
	    "filename", labelWidgetClass, progress_pop,
	    XtNwidth, FILE_WIDTH,
	    XtNvertDistance, FAR_VGAP,
	    XtNfromHoriz, from_label,
	    XtNhorizDistance, 0,
	    NULL);
	dialog_match_dimension(from_label, from_file, XtNheight);

	to_label = XtVaCreateManagedWidget(
	    "toLabel", labelWidgetClass, progress_pop,
	    XtNfromVert, from_label,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    NULL);
	to_file = XtVaCreateManagedWidget(
	    "filename", labelWidgetClass, progress_pop,
	    XtNwidth, FILE_WIDTH,
	    XtNfromVert, from_label,
	    XtNvertDistance, FAR_VGAP,
	    XtNfromHoriz, to_label,
	    XtNhorizDistance, 0,
	    NULL);
	dialog_match_dimension(to_label, to_file, XtNheight);

	dialog_match_dimension(from_label, to_label, XtNwidth);

	waiting = XtVaCreateManagedWidget(
	    "waiting", labelWidgetClass, progress_pop,
	    XtNfromVert, to_label,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    XtNmappedWhenManaged, False,
	    NULL);

	ft_status = XtVaCreateManagedWidget(
	    "status", labelWidgetClass, progress_pop,
	    XtNfromVert, to_label,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    XtNresizable, True,
	    XtNmappedWhenManaged, False,
	    NULL);
	XtVaGetValues(ft_status, XtNlabel, &status_string, NULL);
	status_string = XtNewString(status_string);

	aborting = XtVaCreateManagedWidget(
	    "aborting", labelWidgetClass, progress_pop,
	    XtNfromVert, to_label,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    XtNmappedWhenManaged, False,
	    NULL);

	cancel_button = XtVaCreateManagedWidget(
	    ObjCancelButton, commandWidgetClass, progress_pop,
	    XtNfromVert, ft_status,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    NULL);
	XtAddCallback(cancel_button, XtNcallback, progress_cancel_callback,
	    NULL);
}

/* Callbacks for the "in progress" pop-up. */

/* In-progress pop-up popped up. */
static void
progress_popup_callback(Widget w unused, XtPointer client_data unused,
	XtPointer call_data unused)
{
	XtVaSetValues(from_file, XtNlabel,
	    receive_flag ? ft_host_filename : ft_local_filename, NULL);
	XtVaSetValues(to_file, XtNlabel,
	    receive_flag ? ft_local_filename : ft_host_filename, NULL);

	switch (ft_state) {
	    case FT_AWAIT_ACK:
		XtUnmapWidget(ft_status);
		XtUnmapWidget(aborting);
		XtMapWidget(waiting);
		break;
	    case FT_RUNNING:
		XtUnmapWidget(waiting);
		XtUnmapWidget(aborting);
		XtMapWidget(ft_status);
		break;
	    case FT_ABORT_WAIT:
	    case FT_ABORT_SENT:
		XtUnmapWidget(waiting);
		XtUnmapWidget(ft_status);
		XtMapWidget(aborting);
		break;
	    default:
		break;
	}
}

/* In-progress "cancel" button. */
static void
progress_cancel_callback(Widget w unused, XtPointer client_data unused,
	XtPointer call_data unused)
{
	if (ft_state == FT_RUNNING) {
		ft_state = FT_ABORT_WAIT;
		XtUnmapWidget(waiting);
		XtUnmapWidget(ft_status);
		XtMapWidget(aborting);
	} else {
		/* Impatient user or hung host -- just clean up. */
		ft_complete(get_message("ftUserCancel"));
	}
}

/* "Overwrite existing?" pop-up. */

/* Pop up the "overwrite" pop-up. */
static void
popup_overwrite(void)
{
	/* Initialize it. */
	if (overwrite_shell == (Widget)NULL)
		overwrite_popup_init();

	/* Pop it up. */
	popup_popup(overwrite_shell, XtGrabExclusive);
}

/* Initialize the "overwrite" pop-up. */
static void
overwrite_popup_init(void)
{
	Widget overwrite_pop, overwrite_name, okay_button, cancel_button;
	String overwrite_string, label, lf;
	Dimension d;

	/* Create the shell. */
	overwrite_shell = XtVaCreatePopupShell(
	    "ftOverwritePopup", transientShellWidgetClass, toplevel,
	    NULL);
	XtAddCallback(overwrite_shell, XtNpopupCallback, place_popup,
	    (XtPointer)CenterP);
	XtAddCallback(overwrite_shell, XtNpopdownCallback, overwrite_popdown,
	    (XtPointer)NULL);

	/* Create a form structure to contain the other stuff */
	overwrite_pop = XtVaCreateManagedWidget(
	    ObjDialog, formWidgetClass, overwrite_shell,
	    NULL);

	/* Create the widgets. */
	overwrite_name = XtVaCreateManagedWidget(
	    "overwriteName", labelWidgetClass, overwrite_pop,
	    XtNvertDistance, MARGIN,
	    XtNhorizDistance, MARGIN,
	    XtNborderWidth, 0,
	    XtNresizable, True,
	    NULL);
	XtVaGetValues(overwrite_name, XtNlabel, &overwrite_string, NULL);
	XtVaGetValues(local_file, XtNstring, &lf, NULL);
	label = xs_buffer(overwrite_string, lf);
	XtVaSetValues(overwrite_name, XtNlabel, label, NULL);
	XtFree(label);
	XtVaGetValues(overwrite_name, XtNwidth, &d, NULL);
	if ((Dimension)(d + 20) < 400)
		d = 400;
	else
		d += 20;
	XtVaSetValues(overwrite_name, XtNwidth, d, NULL);
	XtVaGetValues(overwrite_name, XtNheight, &d, NULL);
	XtVaSetValues(overwrite_name, XtNheight, d + 20, NULL);

	okay_button = XtVaCreateManagedWidget(
	    ObjConfirmButton, commandWidgetClass, overwrite_pop,
	    XtNfromVert, overwrite_name,
	    XtNvertDistance, FAR_VGAP,
	    XtNhorizDistance, MARGIN,
	    NULL);
	XtAddCallback(okay_button, XtNcallback, overwrite_okay_callback,
	    NULL);

	cancel_button = XtVaCreateManagedWidget(
	    ObjCancelButton, commandWidgetClass, overwrite_pop,
	    XtNfromVert, overwrite_name,
	    XtNvertDistance, FAR_VGAP,
	    XtNfromHoriz, okay_button,
	    XtNhorizDistance, BUTTON_GAP,
	    NULL);
	XtAddCallback(cancel_button, XtNcallback, overwrite_cancel_callback,
	    NULL);
}

/* Overwrite "okay" button. */
static void
overwrite_okay_callback(Widget w unused, XtPointer client_data unused,
	XtPointer call_data unused)
{
	XtPopdown(overwrite_shell);

	allow_overwrite = True;
	if (ft_start()) {
		XtPopdown(ft_shell);
		popup_progress();
	}
}

/* Overwrite "cancel" button. */
static void
overwrite_cancel_callback(Widget w unused, XtPointer client_data unused,
	XtPointer call_data unused)
{
	XtPopdown(overwrite_shell);
}

/* Overwrite pop-up popped down. */
static void
overwrite_popdown(Widget w unused, XtPointer client_data unused,
	XtPointer call_data unused)
{
	XtDestroyWidget(overwrite_shell);
	overwrite_shell = (Widget)NULL;
}
#endif /*]*/

/* External entry points called by ft_dft and ft_cut. */

/* Pop up a message, end the transfer. */
void
ft_complete(const char *errmsg)
{
	/* Close the local file. */
	if (ft_local_file != (FILE *)NULL && fclose(ft_local_file) < 0)
		popup_an_errno(errno, "close(%s)", ft_local_filename);
	ft_local_file = (FILE *)NULL;

	/* Clean up the state. */
	ft_state = FT_NONE;

#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
	/* Pop down the in-progress shell. */
	XtPopdown(progress_shell);
#endif /*]*/

	/* Pop up the text. */
	if (errmsg != CN) {
		char *msg_copy = NewString(errmsg);

		/* Make sure the error message will fit on the display. */
		if (strlen(msg_copy) > 50 && strchr(msg_copy, '\n') == CN) {
			char *s = msg_copy + 50;

			while (s > msg_copy && *s != ' ')
				s--;
			if (s > msg_copy)
				*s = '\n';	/* yikes! */
		}
		popup_an_error(msg_copy);
		Free(msg_copy);
	} else {
		struct timeval t1;
		double kbytes_sec;
		char *buf;

		(void) gettimeofday(&t1, (struct timezone *)NULL);
		kbytes_sec = (double)ft_length / 1024.0 /
			((double)(t1.tv_sec - t0.tv_sec) + 
			 (double)(t1.tv_usec - t0.tv_usec) / 1.0e6);
		buf = Malloc(256);
		(void) sprintf(buf, get_message("ftComplete"), ft_length,
		    kbytes_sec, ft_is_cut ? "CUT" : "DFT");
		if (ft_is_action) {
			sms_info("%s", buf);
			sms_continue();
		}
#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
		else
			popup_an_info(buf);
#endif /*]*/
		Free(buf);
	}
}

/* Update the bytes-transferred count on the progress pop-up. */
void
ft_update_length(void)
{
#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
	char text_string[80];

	/* Format the message */
	sprintf(text_string, status_string, ft_length);

	XtVaSetValues(ft_status, XtNlabel, text_string, NULL);
#endif /*]*/
}

/* Process a transfer acknowledgement. */
void
ft_running(Boolean is_cut)
{
	if (ft_state == FT_AWAIT_ACK)
		ft_state = FT_RUNNING;
	ft_is_cut = is_cut;
	(void) gettimeofday(&t0, (struct timezone *)NULL);
	ft_length = 0;

#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
	XtUnmapWidget(waiting);
#endif /*]*/
	ft_update_length();
#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
	XtMapWidget(ft_status);
#endif /*]*/
}

/* Process a protocol-generated abort. */
void
ft_aborting(void)
{
	if (ft_state == FT_RUNNING || ft_state == FT_ABORT_WAIT) {
		ft_state = FT_ABORT_SENT;
#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
		XtUnmapWidget(waiting);
		XtUnmapWidget(ft_status);
		XtMapWidget(aborting);
#endif /*]*/
	}
}

/* Process a disconnect abort. */
static void
ft_connected(Boolean ignored unused)
{
	if (!CONNECTED && ft_state != FT_NONE)
		ft_complete(get_message("ftDisconnected"));
}

/* Process an abort from no longer being in 3270 mode. */
static void
ft_in3270(Boolean ignored unused)
{
	if (!IN_3270 && ft_state != FT_NONE)
		ft_complete(get_message("ftNot3270"));
}

/*
 * Script/macro action for file transfer.
 *  Transfer(option=value[,...])
 *  Options are:
 *   Direction=send|receive	default receive
 *   HostFile=name		required
 *   LocalFile=name			required
 *   Host=[tso|vm]		default tso
 *   Mode=[ascii|binary]	default ascii
 *   Cr=[add|remove|keep]	default add/remove
 *   Exist=[keep|replace|append]	default keep
 *   Recfm=[default|fixed|variable|undefined] default default
 *   Lrecl=n			no default
 *   Blksize=n			no default
 *   Allocation=[default|tracks|cylinders|avblock] default default
 *   PrimarySpace=n		no default
 *   SecondarySpace=n		no default
 */
static struct {
	const char *name;
	char *value;
	const char *keyword[4];
} tp[] = {
	{ "Direction",		CN, { "receive", "send" } },
	{ "HostFile" },
	{ "LocalFile" },
	{ "Host",		CN, { "tso", "vm" } },
	{ "Mode",		CN, { "ascii", "binary" } },
	{ "Cr",			CN, { "auto", "remove",	"add", "keep" } },
	{ "Exist",		CN, { "keep", "replace", "append" } },
	{ "Recfm",		CN, { "default", "fixed", "variable",
				      "undefined" } },
	{ "Lrecl" },
	{ "Blksize" },
	{ "Allocation",		CN, { "default", "tracks", "cylinders",
				      "avblock" } },
	{ "PrimarySpace" },
	{ "SecondarySpace" },
	{ CN }
};
#define PARM_DIRECTION		0
#define PARM_HOST_FILE		1
#define PARM_LOCAL_FILE		2
#define PARM_HOST		3
#define PARM_MODE		4
#define PARM_CR			5
#define PARM_EXIST		6
#define PARM_RECFM		7
#define PARM_LRECL		8
#define PARM_BLKSIZE		9
#define PARM_ALLOCATION		10
#define PARM_PRIMARY_SPACE	11
#define PARM_SECONDARY_SPACE	12
#define N_PARMS			(PARM_SECONDARY_SPACE + 1)

void  
Transfer_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	int i, k;
	Cardinal j;
	long l;
	char *ptr;

	char opts[80];
	char *op = opts + 1;
	char *cmd;
	unsigned flen;

        action_debug(Transfer_action, event, params, num_params);

	ft_is_action = True;

	/* Make sure we're connected. */
	if (!IN_3270) {
		popup_an_error("Not connected");
		return;
	}

	/* Set everything to the default. */
	for (i = 0; i < N_PARMS; i++) {
		Free(tp[i].value);
		if (tp[i].keyword[0] != CN)
			tp[i].value =
				NewString(tp[i].keyword[0]);
		else
			tp[i].value = CN;
	}

	/* See what they specified. */
	for (j = 0; j < *num_params; j++) {
		for (i = 0; i < N_PARMS; i++) {
			char *eq;
			int kwlen;

			eq = strchr(params[j], '=');
			if (eq == CN || eq == params[j] || !*(eq + 1)) {
				popup_an_error("Invalid option syntax: '%s'",
					params[j]);
				return;
			}
			kwlen = eq - params[j];
			if (!strncasecmp(params[j], tp[i].name, kwlen)
					&& !tp[i].name[kwlen]) {
				if (tp[i].keyword[0]) {
					for (k = 0;
					     tp[i].keyword[k] != CN && k < 4;
					     k++) {
						if (!strcasecmp(eq + 1,
							tp[i].keyword[k])) {
							break;
						}
					}
					if (k >= 4 ||
					    tp[i].keyword[k] == CN) {
						popup_an_error("Invalid option "
							"value: '%s'", eq + 1);
						return;
					}
				} else switch (i) {
				    case PARM_LRECL:
				    case PARM_BLKSIZE:
				    case PARM_PRIMARY_SPACE:
				    case PARM_SECONDARY_SPACE:
					l = strtol(eq + 1, &ptr, 10);
					if (ptr == eq + 1 || *ptr) {
						popup_an_error("Invalid option "
							"value: '%s'", eq + 1);
						return;
					}
					break;
				    default:
					break;
				}
				tp[i].value = NewString(eq + 1);
				break;
			}
		}
		if (i >= N_PARMS) {
			popup_an_error("Unknown option: %s", params[j]);
			return;
		}
	}

	/* Check for required values. */
	if (tp[PARM_HOST_FILE].value == CN) {
		popup_an_error("Missing 'HostFile' option");
		return;
	}
	if (tp[PARM_LOCAL_FILE].value == CN) {
		popup_an_error("Missing 'LocalFile' option");
		return;
	}

	/*
	 * Start the transfer.  Much of this is duplicated from ft_start()
	 * and should be made common.
	 */

	receive_flag = !strcasecmp(tp[PARM_DIRECTION].value, "receive");
	append_flag = !strcasecmp(tp[PARM_EXIST].value, "append");
	allow_overwrite = !strcasecmp(tp[PARM_EXIST].value, "replace");
	ascii_flag = !strcasecmp(tp[PARM_MODE].value, "ascii");
	if (!strcasecmp(tp[PARM_CR].value, "auto")) {
		cr_flag = ascii_flag;
	} else {
		cr_flag = !strcasecmp(tp[PARM_CR].value, "remove") ||
			  !strcasecmp(tp[PARM_CR].value, "add");
	}
	vm_flag = !strcasecmp(tp[PARM_HOST].value, "vm");
	recfm = DEFAULT_RECFM;
	for (k = 0; tp[PARM_RECFM].keyword[k] != CN && k < 4; k++) {
		if (!strcasecmp(tp[PARM_RECFM].value,
			    tp[PARM_RECFM].keyword[k]))  {
			recfm = (enum recfm)k;
			break;
		}
	}
	units = DEFAULT_UNITS;
	for (k = 0; tp[PARM_ALLOCATION].keyword[k] != CN && k < 4; k++) {
		if (!strcasecmp(tp[PARM_ALLOCATION].value,
			    tp[PARM_ALLOCATION].keyword[k]))  {
			units = (enum units)k;
			break;
		}
	}

	ft_host_filename = tp[PARM_HOST_FILE].value;
	ft_local_filename = tp[PARM_LOCAL_FILE].value;

	/* See if the local file can be overwritten. */
	if (receive_flag && !append_flag && !allow_overwrite) {
		ft_local_file = fopen(ft_local_filename, "r");
		if (ft_local_file != (FILE *)NULL) {
			(void) fclose(ft_local_file);
			popup_an_error("File exists");
			return;
		}
	}

	/* Open the local file. */
	ft_local_file = fopen(ft_local_filename,
	    receive_flag ?
		(append_flag ? "a" : "w" ) :
		"r");
	if (ft_local_file == (FILE *)NULL) {
		popup_an_errno(errno, "Open(%s)", ft_local_filename);
		return;
	}

	/* Build the ind$file command */
	op[0] = '\0';
	if (ascii_flag)
		strcat(op, " ascii");
	if (cr_flag)
		strcat(op, " crlf");
	if (append_flag && !receive_flag)
		strcat(op, " append");
	if (!receive_flag) {
		if (!vm_flag) {
			if (recfm != DEFAULT_RECFM) {
				/* RECFM Entered, process */
				strcat(op, " recfm(");
				switch (recfm) {
				    case FIXED:
					strcat(op, "f");
					break;
				    case VARIABLE:
					strcat(op, "v");
					break;
				    case UNDEFINED:
					strcat(op, "u");
					break;
				    default:
					break;
				};
				strcat(op, ")");
				if (tp[PARM_LRECL].value != CN)
					sprintf(eos(op), " lrecl(%s)",
					    tp[PARM_LRECL].value);
				if (tp[PARM_BLKSIZE].value != CN)
					sprintf(eos(op), " blksize(%s)",
					    tp[PARM_BLKSIZE].value);
			}
			if (units != DEFAULT_UNITS) {
				/* Space Entered, processs it */
				switch (units) {
				    case TRACKS:
					strcat(op, " tracks");
					break;
				    case CYLINDERS:
					strcat(op, " cylinders");
					break;
				    case AVBLOCK:
					strcat(op, " avblock");
					break;
				    default:
					break;
				};
				if (tp[PARM_PRIMARY_SPACE].value != CN) {
					sprintf(eos(op), " space(%s",
					    tp[PARM_PRIMARY_SPACE].value);
					if (tp[PARM_SECONDARY_SPACE].value)
						sprintf(eos(op), ",%s",
						    tp[PARM_SECONDARY_SPACE].value);
					strcat(op, ")");
				}
			}
		} else {
			if (recfm != DEFAULT_RECFM) {
				strcat(op, " recfm ");
				switch (recfm) {
				    case FIXED:
					strcat(op, "f");
					break;
				    case VARIABLE:
					strcat(op, "v");
					break;
				    default:
					break;
				};

				if (tp[PARM_LRECL].value)
					sprintf(eos(op), " lrecl %s",
					    tp[PARM_LRECL].value);
			}
		}
	}

	/* Insert the '(' for VM options. */
	if (strlen(op) > 0 && vm_flag) {
		opts[0] = ' ';
		opts[1] = '(';
		op = opts;
	}

	/* Build the whole command. */
	cmd = xs_buffer("%s %s %s%s\\n",
	    appres.ft_command,
	    receive_flag ? "get" : "put", ft_host_filename, op);

	/* Erase the line and enter the command. */
	flen = kybd_prime();
	if (!flen || flen < strlen(cmd) - 1) {
		Free(cmd);
		popup_an_error(get_message("ftUnable"));
		return;
	}
	(void) emulate_input(cmd, strlen(cmd), False);
	Free(cmd);

	/* Get this thing started. */
	ft_state = FT_AWAIT_ACK;
	ft_is_cut = False;

	/* Start tracking it. */
#if defined(X3270_DISPLAY) && defined(X3270_MENUS) /*[*/
	popup_progress();
#endif /*]*/
}

#endif /*]*/
