/*
 * Discuss-Meeting widget
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <com_err.h>
#include <sys/file.h>
#include <string.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Command.h>
#include <X11/Shell.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Form.h>
#include <X11/cursorfont.h>
#include <discuss/discuss.h>

#include "MeetingP.h"
#include "Meeting.h"

#define malloc(u)	((void *)XtMalloc(u))
#define realloc(p,u)	((void *)XtRealloc(p,u))
#define free(p)		XtFree(p)

/* GNU C compiler: Handles alloca and non-returning functions.  */
#if defined(__GNUC__)
#define no_return volatile
#undef alloca
#define alloca __builtin_alloca
#else
#define no_return
#endif

/* High C compiler: Handles alloca.  */
#ifdef __HIGHC__
#undef alloca
#define alloca _Alloca
#endif /* __HIGHC__ */

/* Sparc compiler has its own alloca.  */
#if defined (sparc) && !defined (__GNUC__)
#include <alloca.h>
#endif

static char const rcsid[] =
    "$Header: /afs/sipb.mit.edu/project/discuss/.cvsroot/discuss/source/xdsc/Meeting.c,v 1.8 1992/06/26 02:25:24 raeburn Exp $";

extern tfile unix_tfile(), mem_tfile ();

#ifndef alloca
extern void * alloca (unsigned);
#endif
extern int errno;

/* */

static void Initialize (), Destroy (), ClassInitialize ();

static /*const*/ name_blk *no_mtg = NULL;
static /*const*/ Boolean Nil = False;
static /*const*/ String no_name = "";
static /*const*/ String flag_true = "flag set";
static /*const*/ String flag_false = "flag clear";

/*
 * Resources for this widget.  Cannot be `const' because the toolkit
 * will modify this structure in place.
 */

static XtResource discussMeetingResources[] = {
    { XtNmeeting, XtCmeeting, XtRPointer, sizeof (name_blk *),
	  XtOffset (DiscussMeetingWidget, discuss_meeting.nb),
	  XtRPointer, (char *) &no_mtg },
    { XtNmeetingName, XtCmeetingName, XtRString, sizeof (String),
	  XtOffset (DiscussMeetingWidget, discuss_meeting.mtg_name),
	  XtRString, (char *) "" },
    { XtNlavin, XtClavin, XtRBoolean, sizeof (Boolean),
	  XtOffset (DiscussMeetingWidget, discuss_meeting.lavin),
	  XtRBoolean, (char *) & Nil },
    { XtNflagsFromInitial, XtCflagsFromInitial, XtRBoolean, sizeof (Boolean),
	  XtOffset (DiscussMeetingWidget, discuss_meeting.flags_from_initial),
	  XtRBoolean, (char *) & Nil },
    { XtNquitCallback, XtCquitCallback, XtRCallback, sizeof (XtPointer),
	  XtOffset (DiscussMeetingWidget, discuss_meeting.quit_callbacks),
	  XtRCallback, (XtPointer) NULL },
};

extern const WidgetClass discussMeetingWidgetClass;

#if !defined(__GNUC__) \
    || (__GNUC__ == 1 && defined(ibm032)) /* rt gcc 1 has problems inlining */
#define inline
#else
#define inline __inline__
#endif

typedef struct {
    char s[16];
} time_buf;

/* ctime format is "Sun Sep 16 01:03:52 1973\0" */
/*                  0         1         2   2   */
/*                  0         0         0   4   */
/* output format is "mm/dd/yy hh:mm\0"          */
/*                   0         1                */

typedef struct {
    long quot;
    long rem;
} ldiv_t;

static inline ldiv_t ldiv (long int numer, long int denom) {
    ldiv_t result;
    result.quot = numer / denom;
    result.rem = numer % denom;
    return result;
}
#if defined(__GNUC__)
#if defined(__vax__)
static inline ldiv_t Ivax_ldiv (long int numer, long int denom) {
    ldiv_t result = { 0, 0 };
    ldiv_t input = { numer, 0 };
    asm ("ediv %2,%3,%0,%1"
	 : "=g" (result.quot), "=g" (result.rem)
	 : "g" (denom), "g" (input));
    return result;
}
#define ldiv(x,y) Ivax_ldiv(x,y)
#endif /* machine type != vax */
#endif /* __GNUC__ */

static inline time_buf short_time(time)
    unsigned long *time;
{
    register struct tm *now;
    static const time_buf t_init = { "xx/xx/xx xx:xx" };
    time_buf t;

    t = t_init;
    now = localtime(time);
#define put(n,o) {ldiv_t r;r=ldiv(n,10UL);t.s[o]='0'+r.quot;t.s[o+1]='0'+r.rem;}
    now->tm_mon++;
    put(now->tm_mon, 0);
    put(now->tm_mday, 3);
    put(now->tm_year, 6);
    put(now->tm_hour, 9);
    put(now->tm_min, 12);
#undef put
    return(t);
}

static void inline set_label_text (Widget w, const char *text) {
    Arg args[1];
    XtSetArg (args[0], XtNlabel, text);
    XtSetValues(w, args, 1);
}

static void inline show_error (DiscussMeetingWidget w, const char *text) {
    set_label_text (w->discuss_meeting.error, text);
}

static void show_transaction (DiscussMeetingWidget w);

static inline DiscussMeetingWidget get_dsc_widget (Widget w) {
    while (w && w->core.widget_class != discussMeetingWidgetClass)
	w = XtParent (w);
    return (DiscussMeetingWidget) w;
}

static void ClassInitialize () {
    initialize_dsc_error_table ();
}

static void redo (DiscussMeetingWidget w, unsigned trn, const char *msg) {
    if (!w->discuss_meeting.nb) return;
    if (trn) {
	w->discuss_meeting.current = trn;
	show_transaction(w);
    }
    else {
	XBell(XtDisplay(w), 40);
	show_error(w, msg);
    }
}

static void update_info (DiscussMeetingWidget w, long *code_p) {
    long code;
    char buf[BUFSIZ];
    mtg_info new_mtg_info;
    trn_info3 new_trn_info;

    if (!w->discuss_meeting.nb) return;
    if (!code_p)
	code_p = &code;
    dsc_get_mtg_info (w->discuss_meeting.nb, &new_mtg_info, &code);
    if (*code_p)
	return;
    w->discuss_meeting.mtg = new_mtg_info;
    if (w->discuss_meeting.mtg_id) {
	sprintf (buf, "%s meeting, range [%04d]-[%04d]",
		 w->discuss_meeting.mtg_name,
		 w->discuss_meeting.mtg.lowest,
		 w->discuss_meeting.mtg.highest);
	set_label_text(w->discuss_meeting.mtg_id, buf);
    }
    dsc_get_trn_info3 (w->discuss_meeting.nb,
		       w->discuss_meeting.current, &new_trn_info, &code);
    if (*code_p)
	return;
    w->discuss_meeting.trn = new_trn_info;
    return;
}

static void save_info (Widget b, XEvent *e, String *argv, Cardinal *argc) {
    DiscussMeetingWidget w = get_dsc_widget (b);
    int code;
    name_blk *nbp = w->discuss_meeting.nb;

    if (w->discuss_meeting.max_seen > nbp->last)
	nbp->last = w->discuss_meeting.max_seen;
    (void) time(&nbp->date_attended);
    dsc_update_mtg_set((char *)NULL, nbp, 1, &code);
    if (code) {
	com_err("xdsc", code, "saving meeting info");
	w->discuss_meeting.flags.save_ok = 0;
    }
    else
	w->discuss_meeting.flags.save_ok = 1;
}

static void abort_mtg (Widget b, XEvent *e, String *argv, Cardinal *argc) {
    DiscussMeetingWidget w = get_dsc_widget (b);
    if (!w->discuss_meeting.nb) return;
    XtCallCallbackList ((Widget) w, w->discuss_meeting.quit_callbacks, NULL);
}

static void quit_mtg (Widget b, XEvent *e, String *argv, Cardinal *argc) {
    DiscussMeetingWidget w;
    w = get_dsc_widget (b);
    if (!w->discuss_meeting.nb) {
	XtCallActionProc (b, "unset", 0, 0, 0);
	return;
    }
    save_info (b, e, argv, argc);
    if (w->discuss_meeting.flags.save_ok)
	abort_mtg (b, e, argv, argc);
    else
	XtCallActionProc (b, "unset", 0, 0, 0);
}

static void change_trn (Widget b, XEvent *e, String *argv, Cardinal *argc) {
    static const struct {
	const char *str;
	int offset;
	const char *msg;
    } actions[] = {
#define Do(name,field,msg) { name, \
				 XtOffset (DiscussMeetingWidget,\
					   discuss_meeting.field),\
					       msg }
	Do ("Next", trn.next, "no next transaction"),
	Do ("Prev", trn.prev, "no previous transaction"),
	Do ("Next_Ref", trn.nref, "no next reference"),
	Do ("Prev_Ref", trn.pref, "no previous reference"),
	Do ("First_Ref", trn.fref, "no fref?"),
	Do ("Last_Ref", trn.lref, "no lref?"),
	Do ("First", mtg.first, "no first transaction in meeting!?"),
	Do ("Last", mtg.last, "no last transaction in meeting!?"),
    };
    DiscussMeetingWidget w = get_dsc_widget (b);
    int *ptr, i;

    if (!w->discuss_meeting.nb) return;
    for (i = 0; i < XtNumber (actions); i++)
	if (!strcmp (actions[i].str, argv[0]))
	    break;
    if (i == XtNumber (actions)) {
	com_err ("xdsc", 0, "bogon action string %s", argv[0]);
	return;
    }
    ptr = (int *)((char *) w + actions[i].offset);
    if (!*ptr)
	update_info (w, 0);
    redo (w, *ptr, actions[i].msg);
}

static void skip_chain (DiscussMeetingWidget w, const int direction) {
    trn_info3 new_info;
    int new_trn, code;
    char *msg;

    if (!w->discuss_meeting.nb) return;
    new_trn = w->discuss_meeting.trn.fref;
    while (direction) {
	new_trn += direction;
	if (new_trn == 0) {
	    msg = "No previous chain";
	    goto oops;
	}
	dsc_get_trn_info3 (w->discuss_meeting.nb, new_trn, &new_info, &code);
	if (code == DELETED_TRN)
	    continue;
	if (code == NO_SUCH_TRN) {
	    msg = "No next chain";
	    goto oops;
	}
	if (new_info.pref != 0)
	    continue;
	break;
    }
    redo (w, new_trn, "Oops!  Can't read first transaction from chain!");
    return;
oops:
    show_error (w, msg);
}

static Boolean destroy_window_wp (XtPointer w) {
    XtDestroyWidget ((Widget) w);
    return True;
}

static void destroy_widget_soon (Widget w) {
    XtAppAddWorkProc (XtWidgetToApplicationContext (w),
		      destroy_window_wp, w);
}

struct edit_data {
    DiscussMeetingWidget w;
    Widget popup, body, subject;
    int original;
};

static void cancel_edit (Widget button, caddr_t p, caddr_t call) {
    struct edit_data *data = (struct edit_data *) p;
    Widget shell = data->popup;
    XtPopdown (shell);
    data->w->discuss_meeting.n_edits--;
    data->w = 0;
    data->popup = 0;
    XtFree ((char *) data);
    destroy_widget_soon (shell);
}

static void enter_transaction (Widget button, caddr_t p, caddr_t call) {
    struct edit_data *data = (struct edit_data *) p;
    int new_num;
    int code = 0;
    Arg args[2];
    tfile tf_text;
    char *text;
    char *subject;

    XtSetArg (args[0], XtNstring, &text);
    XtGetValues (data->body, args, 1);
    XtSetArg (args[0], XtNstring, &subject);
    XtGetValues (data->subject, args, 1);
    tf_text = mem_tfile (text, strlen (text));
    dsc_add_trn (data->w->discuss_meeting.nb, tf_text, subject,
		 data->original, &new_num, &code);
    if (code) {
	com_err ("xdsc", code, "entering transaction...");
	return;
    }
    tclose (tf_text, &code);
    code = tdestroy (tf_text);
    cancel_edit (button, p, call);
}

static void popup_edit_window (DiscussMeetingWidget w, int original) {
    Widget popup, pane, commands, subject_prompt, subject_label;
    Arg args[30];
    XtCallbackRec callback[2];
    char buf[BUFSIZ], *subj;
    int n_args, width;
    struct edit_data *data;
#define AddArg(x,y)	do{XtSetArg(args[n_args],x,y);n_args++;}while(0)

    data = (struct edit_data *) XtMalloc (sizeof (struct edit_data));
    data->w = w;
    data->original = original;
    popup = XtCreatePopupShell ("edit", topLevelShellWidgetClass, (Widget) w,
				0, 0);
    data->popup = popup;
    pane = XtCreateManagedWidget ("pane", panedWidgetClass, popup,
				  0, 0);
    n_args = 0;
    if (original)
	sprintf (buf, "reply to transaction %d in meeting `%s'",
		 original, w->discuss_meeting.nb->aliases[0]);
    else
	sprintf (buf, "new transaction for meeting `%s'",
		 w->discuss_meeting.nb->aliases[0]);
    AddArg (XtNlabel, buf);
    XtCreateManagedWidget ("label", labelWidgetClass, pane,
				   args, n_args);
    n_args = 0;
    commands = XtCreateManagedWidget ("commands", boxWidgetClass, pane,
				      args, n_args);
    callback[0].callback = enter_transaction;
    callback[0].closure = (char *) data;
    callback[1].callback = 0;
    AddArg (XtNcallback, callback);
    XtCreateManagedWidget ("enter", commandWidgetClass, commands,
				   args, n_args);
    callback[0].callback = cancel_edit;
    XtCreateManagedWidget ("cancel", commandWidgetClass, commands,
				    args, n_args);
    n_args = 0;
    subject_prompt = XtCreateManagedWidget ("subject_prompt",
					    formWidgetClass, pane,
					    args, n_args);
    AddArg (XtNlabel, "Subject: ");
    AddArg (XtNresizable, True);
    AddArg (XtNborderWidth, 0);
    subject_label = XtCreateWidget ("subject_label", labelWidgetClass,
				    subject_prompt, args, n_args);
    n_args = 0;
    AddArg (XtNwidth, &width);
    /* figure out how to fix size at minimum needed */
    XtManageChild (subject_label);
    n_args = 0;
    if (original) {
	subj = w->discuss_meeting.trn.subject;
	if (!subj)
	    subj = "";
	if (!((subj[0] == 'R' || subj[0] == 'r')
	      && (subj[1] == 'e' || subj[1] == 'E')
	      && subj[2] == ':' && subj[3] == ' ')) {
	    subj = alloca (strlen (subj) + 5);
	    strcpy (subj, "Re: ");
	    strcat (subj, (w->discuss_meeting.trn.subject
			   ? w->discuss_meeting.trn.subject
			   : ""));
	}
    }
    else
	subj = "";
    AddArg (XtNstring, subj);
    AddArg (XtNeditType, XawtextEdit);
    AddArg (XtNborderWidth, 0);
    AddArg (XtNfromHoriz, subject_label);
    data->subject = XtCreateManagedWidget ("subject", asciiTextWidgetClass,
					   subject_prompt, args, n_args);
    n_args = 0;
    AddArg (XtNstring, "");
    AddArg (XtNeditType, XawtextEdit);
    data->body = XtCreateManagedWidget ("body", asciiTextWidgetClass, pane,
					args, n_args);
    w->discuss_meeting.n_edits++;
    XtPopup (popup, XtGrabNone);
}

/*
 * MAJOR KLUDGE ALERT!!!
 */
static void run_xterm_command (const char *cmd, const char *name, int scroll) {
    const char *argv[20];
    int i = 0;
    argv[i++] = "xterm";
    argv[i++] = "-name", argv[i++] = name;
    argv[i++] = "-title", argv[i++] = name;
    if (scroll) {
	argv[i++] = "-sb";
	argv[i++] = "-sl", argv[i++] = "1000";
    }
    argv[i++] = "-e", argv[i++] = "csh", argv[i++] = "-fc", argv[i++] = cmd;
    argv[i++] = 0;
    switch (vfork()) {
    case -1:
	perror ("vfork");
	return;
    case 0:
	/* we are the child */
	break;
    default:
	sleep (2);
	return;
    };
    execvp (argv[0], argv);
    perror (argv[0]);
    _exit (1);
}

#if 0
static void run_discuss_command (const char *mtg, const char *cmd, const char *name) {
    char buf[BUFSIZ];
    sprintf (buf, "stty dec;reset;exec discuss '%s' -request '%s' -quit",
	     mtg, cmd);
    run_xterm_command (buf, name, 0);
}
#endif

static void reply (Widget b, XEvent *e, String *argv, Cardinal *P_argc) {
    DiscussMeetingWidget w = get_dsc_widget (b);
    Cardinal argc = *P_argc;
    int rnd = 0, orig;
    while (argc--) {
	if (!strcmp ("Random", *argv))
	    rnd = 1;
	else
	    com_err ("xdsc", 0, "reply: unknown arg %s\n", *argv);
	++argv;
    };
    if (!w->discuss_meeting.nb) return;
    orig = (rnd
	    ? (random () % w->discuss_meeting.mtg.last)
	    : w->discuss_meeting.current);
    popup_edit_window (w, orig);
}

static void talk (Widget b, XEvent *e, String *argv, Cardinal *argc) {
    DiscussMeetingWidget w = get_dsc_widget (b);
    if (!w->discuss_meeting.nb) return;
    popup_edit_window (w, 0);
}

static void chain (Widget b, XEvent *e, String *argv, Cardinal *P_argc) {
    int direction = 0, argc = *P_argc;
    while (argc) {
	if (!strcmp (*argv, "Next"))
	    direction = 1;
	else if (!strcmp (*argv, "Prev"))
	    direction = -1;
    }
    if (direction)
	skip_chain (get_dsc_widget (b), direction);
}

static void list_em (Widget button, caddr_t p, caddr_t call) {
    DiscussMeetingWidget w = (DiscussMeetingWidget) p;
    char *buf, *buf1;
    int len;

    if (!w->discuss_meeting.nb) return;
    len = strlen (w->discuss_meeting.nb->aliases[0]);
    len += 15;
    buf1 = alloca (len);
    buf = alloca (3 * len + 120);
    strcpy (buf1, "\"listing of ");
    strcat (buf1, w->discuss_meeting.nb->aliases[0]);
    strcat (buf1, "\"");
    sprintf (buf, "reset;discuss \"%s\" -rq \"ls -initial -flag_reset\" -quit",
	     w->discuss_meeting.nb->aliases[0]);
    strcat (buf, ";exec sleep 999999");
    run_xterm_command (buf, buf1, 1);
}

static void list (Widget b, XEvent *e, String *argv, Cardinal *argc) {
    list_em (b, (caddr_t) get_dsc_widget (b), 0);
}

static XtActionsRec actionTable[8] = {
    { "Goto",	(XtActionProc) change_trn },
    { "Punt",	(XtActionProc) abort_mtg },
    { "Quit",	(XtActionProc) quit_mtg },
    { "Sync",	(XtActionProc) save_info },
    { "Talk",	(XtActionProc) talk },
    { "Reply",	(XtActionProc) reply },
    { "Chain",	(XtActionProc) chain },
    { "List",	(XtActionProc) list },
};

static void radio (Widget button, caddr_t p, caddr_t call) {
    DiscussMeetingWidget w = (DiscussMeetingWidget) p;
    int code, value;
    caddr_t current_toggle;
    char buf[BUFSIZ];
    name_blk *nbp = w->discuss_meeting.nb;

    if (!w->discuss_meeting.flags.accepting_input)
	return;
    value = w->discuss_meeting.trn.flags;
    current_toggle = XawToggleGetCurrent (w->discuss_meeting.radio);
    if (!current_toggle)
	return;
    if (current_toggle == (caddr_t) &flag_true)
	value |= TRN_FLAG1;
    else
	value &= ~TRN_FLAG1;
    dsc_set_trn_flags (nbp,
		       w->discuss_meeting.flags_from_initial
		       ? w->discuss_meeting.trn.fref
		       : w->discuss_meeting.current,
		       value, &code);
    if (!code)
	return;
    sprintf (buf, "Can't alter flag: %s", error_message (code));
    show_error (w, buf);
}

static inline Widget add_a_button (Widget box, const char *name) {
    return XtCreateWidget (name, commandWidgetClass, box, 0, 0);
}

static void show_transaction (DiscussMeetingWidget w) {
    int code;
    char buf[BUFSIZ];
#define info (&w->discuss_meeting)
    char *cp;
    char *buf2;
    name_blk *nbp = w->discuss_meeting.nb;
    int len, flag;
    int trn = w->discuss_meeting.current;
    tfile tf;
    time_buf when;
    Arg src_arg[3];

    show_error(w, " ");
    if (!nbp)
	return;

    dsc_get_trn_info3(w->discuss_meeting.nb, w->discuss_meeting.current,
		      &w->discuss_meeting.trn, &code);
    if (code) {
	com_err("xdsc", code, "getting info on transaction %d",
		w->discuss_meeting.current);
	return;
    }

    w->discuss_meeting.flags.accepting_input = 0;
    len = w->discuss_meeting.trn.num_chars;
    buf2 = malloc (len + 10);
    bzero (buf2+len-5, 10);
    tf = mem_tfile (buf2, len+1);
    dsc_get_trn (nbp, trn, tf, &code);
    if (code)
	goto lost_text;
    tclose(tf, &code);
    if (code)
	goto lost_text;
    (void) tdestroy (tf);
    XtSetArg (src_arg[0], XtNstring, buf2);
    XtSetArg (src_arg[1], XtNlength, w->discuss_meeting.trn.num_chars);
    XtSetValues (w->discuss_meeting.text, src_arg, 2);
    if (w->discuss_meeting.buffer)
	free (w->discuss_meeting.buffer);
    w->discuss_meeting.buffer = buf2;
lost_text:
    if (code) {
	com_err ("xdsc", code, "reading transaction %d", trn);
	w->discuss_meeting.flags.accepting_input = 1;
	return;
    }

    when = short_time((unsigned long *) &info->trn.date_entered);
    sprintf(buf, "[%04d] %s  %s (%d line%s)",
	    info->trn.current, info->trn.author, when.s,
	    info->trn.num_lines, info->trn.num_lines == 1 ? "" : "s");
    set_label_text(info->header, buf);
    strcpy(buf, "Subject: ");
    strcat(buf, info->trn.subject);
    set_label_text(info->subject, buf);
    if (!code && info->trn.signature)
	set_label_text (info->error, info->trn.signature);
    sprintf(buf, "--[%04d]--", info->trn.current);
    if (info->trn.pref || info->trn.nref) {
	for (cp = buf; *cp; cp++)
	    ;
	if (info->trn.pref && info->trn.nref)
	    sprintf(cp, " (pref = [%04d], nref = [%04d])",
		    info->trn.pref, info->trn.nref);
	else if (info->trn.pref)
	    sprintf(cp, " (pref = [%04d])", info->trn.pref);
	else
	    sprintf(cp, " (nref = [%04d])", info->trn.nref);
    }
    set_label_text(info->footer, buf);
    if (w->discuss_meeting.flags_from_initial) {
	trn_info3 t;
	dsc_get_trn_info3 (w->discuss_meeting.nb, w->discuss_meeting.trn.fref,
			   &t, &code);
	if (code) {
	    sprintf (buf, "Can't read flag information: %s",
		     error_message (code));
	    show_error (w, buf);
	    XawToggleUnsetCurrent (w->discuss_meeting.radio);
	}
	flag = t.flags & TRN_FLAG1;
	goto twiddle_toggles;
    }
    else {
	register caddr_t data;
	flag = w->discuss_meeting.trn.flags & TRN_FLAG1;
    twiddle_toggles:
	data = (caddr_t) (flag ? &flag_true : &flag_false);
	XawToggleSetCurrent (w->discuss_meeting.radio, data);
    }
    if (info->current > info->max_seen)
	info->max_seen = info->current;
    w->discuss_meeting.flags.accepting_input = 1;
#undef info
}

#define add_label(pw,name,pos,args,argc) \
    XtCreateManagedWidget(name,labelWidgetClass,pw,args,argc)

struct _proc {
    const char *label;
    int when;
};
static const struct _proc mtg_cmds[] = {
    { "prev",	3, },
    { "next",	3, },
    { "first",	3, },
    { "last",	3, },
    { 0, },
};
static const struct _proc chain_cmds[] = {
    { "pref",	3, },
    { "nref",	3, },
    { "fref",	3, },
    { "lref",	3, },
    { 0, },
};
static const struct _proc misc_cmds[] = {
    { "talk",	3, },
    { "reply",	3, },
    { "randrp",	1, },
    { "pchain", 2, },
    { "nchain", 2, },
    { "ilist",	2, },
    { "quit",	3, },
    { "abort",	3, },
    { NULL, },
};

static const Arg text_args[] = {
    { XtNstring,	(XtArgVal) "",		},
};

static void build_command_box (Widget new,
			       const char * name,
			       const char * label,
			       const struct _proc *procs,
			       int which) {
    Arg args[2];
    Widget box, buttons[10], *b = buttons;
#ifdef __SABER__
    /* bug workaround */
    struct _proc *p = (struct _proc *) procs;
#else
    const struct _proc *p = procs;
#endif

    XtSetArg(args[0], XtNname, name);
    box = XtCreateManagedWidget (name, boxWidgetClass, new, args, 1);
    if (label) {
	XtSetArg (args[0], XtNborderWidth, 0);
	XtSetArg (args[1], XtNlabel, label);
	/* discard the pointer */
	(void) XtCreateManagedWidget ("label", labelWidgetClass, box, args, 2);
    }
    for (; p->label; p++) {
	if (p->when & which)
	    *b++ = add_a_button (box, p->label);
    }
    XtManageChildren (buttons, b - buttons);
}    

static void Initialize (Widget request, Widget new) {
    DiscussMeetingWidget w = (DiscussMeetingWidget) new;
    Arg label_args[10];
    name_blk *nbp;
    char buf[BUFSIZ];
    char *error_text = NULL;
    int not_ok = 0, now;
    long code;

    bzero (&w->discuss_meeting.flags, sizeof (w->discuss_meeting.flags));
    nbp = w->discuss_meeting.nb;
    if (!nbp && w->discuss_meeting.mtg_name && *w->discuss_meeting.mtg_name) {
	w->discuss_meeting.nb = nbp = malloc (sizeof (name_blk));
	w->discuss_meeting.flags.nb_internal = 1;
	dsc_get_mtg ((char *) NULL, w->discuss_meeting.mtg_name, nbp, &code);
	if (code) {
	    sprintf (buf, "DiscussMeeting::Initialize: %s (meeting \"%s\")",
		     error_message (code), w->discuss_meeting.mtg_name);
	    XtAppError (XtWidgetToApplicationContext (new), buf);
	    return;
	}
    }
    if (nbp) {
	dsc_get_mtg_info (nbp, &w->discuss_meeting.mtg, &code);
	if (code) {
	    char *msg = alloca (strlen (error_message (code)) + 1);
	    strcpy (msg, error_message (code));
	    error_text = msg;
	}
    }

    /*
     * Create button box for command buttons, in position 1.
     */
    now = not_ok ? 4 : !nbp ? 1 : (w->discuss_meeting.lavin) ? 2 : 1;
    if (!not_ok) {
	build_command_box (new, "meeting_cmds", "In meeting:", mtg_cmds, now);
	build_command_box (new, "chain_cmds", "In chain:  ", chain_cmds, now);
    }
    build_command_box (new, "misc", (char *) 0, misc_cmds, now);

    /*
     * Various labels, filling positions 2 through 5 and 7.
     */

    XtSetArg(label_args[1], XtNname,
	     nbp ? w->discuss_meeting.mtg.long_name : "[no meeting]");
    XtSetArg(label_args[0], XtNlabel, "");
    w->discuss_meeting.mtg_id = add_label (new, "id", 2, label_args, 2);
    {
	Arg args[3];
	Dimension height;
	XtSetArg (args[0], XtNheight, &height);
	XtGetValues (w->discuss_meeting.mtg_id, args, 1);
	XtSetArg (label_args[3], XtNmin, height);
	XtSetArg (label_args[4], XtNmax, height);
	XtSetArg (label_args[5], XtNskipAdjust, True);
	XtSetArg (label_args[2], XtNallowResize, False);
	XtSetValues (w->discuss_meeting.mtg_id, label_args, 6);
    }
    w->discuss_meeting.error = add_label(new, "error", 3, label_args, 6);
    if (error_text) {
	Arg args[1];
	XtSetArg (args[0], XtNlabel, error_text);
	XtSetValues (w->discuss_meeting.error, args, 1);
    }
    if (not_ok) {
#define Z(field) w->discuss_meeting.field = NULL
	Z (header);
	Z (text);
	Z (subject);
	Z (radio);
	Z (footer);
	Z (buffer);
#undef Z
	return;
    }
    w->discuss_meeting.header = add_label(new, "header", 4, label_args, 6);
    {
	Widget tbox;
	Arg args[3];
	XtCallbackRec radio_callback[2];
	tbox = XtCreateManagedWidget ("toggles",
				      boxWidgetClass,
				      new, 0, 0);
	radio_callback[0].callback = radio;
	radio_callback[0].closure = (caddr_t) w;
	radio_callback[1].callback = 0;
	/* `true' toggle */
	XtSetArg (args[0], XtNradioData, &flag_true);
	XtSetArg (args[1], XtNradioGroup, NULL);
	XtSetArg (args[2], XtNcallback, radio_callback);
	w->discuss_meeting.radio =
	    XtCreateManagedWidget ("true", toggleWidgetClass,
				   tbox, args, 3);
	/* `false' toggle */
	args[0].value = (XtArgVal) &flag_false;
	args[1].value = (XtArgVal) w->discuss_meeting.radio;
	(void) XtCreateManagedWidget ("false", toggleWidgetClass,
				      tbox, args, 3);
    }
    w->discuss_meeting.subject = add_label(new, "subject", 5, label_args, 6);
    w->discuss_meeting.text =
	XtCreateManagedWidget ("text", asciiTextWidgetClass, new,
			       text_args, XtNumber (text_args));
    w->discuss_meeting.footer = add_label(new, "footer", 7, label_args, 6);
    w->discuss_meeting.buffer = (char *) NULL;
    w->discuss_meeting.current = nbp->last;
    w->discuss_meeting.max_seen = w->discuss_meeting.current;
    XtManageChildren (&w->discuss_meeting.mtg_id, 6);
    if (w->discuss_meeting.nb) {
	static const char * const foo[] = { "Next", "First", "Last" };
	update_info (w, &code);
	if (code)
	    XtCallActionProc (new, "Goto", 0,
			      w->discuss_meeting.current ? foo+1 : foo+2,
			      1);
	else if (w->discuss_meeting.trn.next)
	    XtCallActionProc (new, "Goto", 0, foo, 1);
	else
	    show_transaction (w);
    }
}

static void Destroy () {
    return;
}

/*
 * No `const' here either.
 */
static DiscussMeetingClassRec discussMeetingClassRec = {
  {
    (WidgetClass) &panedClassRec,	/* superclass		  */
    "DiscussMeeting",			/* class_name		  */
    sizeof(DiscussMeetingRec),		/* size			  */
    ClassInitialize,			/* class_initialize	  */
    NULL,				/* class_part_initialize  */
    FALSE,				/* class_inited		  */
    Initialize,				/* initialize		  */
    NULL,				/* initialize_hook	  */
    XtInheritRealize,			/* realize		  */
    actionTable,			/* actions		  */
    XtNumber (actionTable),		/* num_actions		  */
    discussMeetingResources,		/* resources		  */
    XtNumber(discussMeetingResources),	/* resource_count	  */
    NULLQUARK,				/* xrm_class		  */
    FALSE,				/* compress_motion	  */
    TRUE,				/* compress_exposure	  */
    TRUE,				/* compress_enterleave    */
    FALSE,				/* visible_interest	  */
    Destroy,				/* destroy		  */
    XtInheritResize,			/* resize		  */
    NULL,				/* expose		  */
    0,					/* set_values		  */
    NULL,				/* set_values_hook	  */
    XtInheritSetValuesAlmost,		/* set_values_almost	  */
    NULL,				/* get_values_hook	  */
    NULL,				/* accept_focus		  */
    XtVersion,				/* version		  */
    NULL,				/* callback_private	  */
    NULL,				/* tm_table		  */
    XtInheritQueryGeometry,		/* query_geometry	  */
    XtInheritDisplayAccelerator,	/* display_accelerator	  */
    NULL,				/* extension		  */
  },  /* CoreClass fields initialization */
  {
      XtInheritGeometryManager,
      XtInheritChangeManaged,
      XtInheritInsertChild,
      XtInheritDeleteChild,
  },  /* CompositeClass fields initialization */
  {
      0,
      0,
      sizeof (PanedConstraintsRec),
  },  /* ConstraintClass fields initialization */
};

/* for public consumption */
const WidgetClass discussMeetingWidgetClass =
    (WidgetClass) &discussMeetingClassRec;
