/*
 * @(#)xgui.c
 *
 * Copyright 2022 - 2025  David A. Bagley, bagleyd AT verizon.net
 *
 * All rights reserved.
 *
 * 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, and that the name of the author not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * This program is distributed in the hope that it will be "useful",
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

/* Widgets and other GUI for Xt Stuff */

#include "xwin.h"
#include "xgui.h"
#include <X11/Intrinsic.h>

char *progDsp;

void usage(char *programName, const char *synopsisHelp)
{
	unsigned int i;

	(void) fprintf(stderr, "usage: %s\n", programName);
	for (i = 0; i < strlen(synopsisHelp); i++) {
		if (i == 0 || synopsisHelp[i - 1] == '\n')
			(void) fprintf(stderr, "\t");
		(void) fprintf(stderr, "%c", (synopsisHelp[i]));
	}
	(void) fprintf(stderr, "\n");
	exit(1);
}

void printVersionOnly(const char *aboutHelp)
{
	(void) printf("%s\n", aboutHelp);
	exit(0);
}

void concatTitle(char *newTitle, char *title,
		const char *joinString, const char *string, int n)
{
	int stringLen = strlen(string);
	int joinStringLen = strlen(joinString);
	int len = n - stringLen - joinStringLen;

	if (len > 0) {
		(void) strncpy(newTitle, title, len);
		newTitle[len - 1] = '\0';
		(void) strcat(newTitle, joinString);
		(void) strcat(newTitle, string);
	} else {
		(void) strncpy(newTitle, title, n - 1);
		newTitle[n - 1] = '\0';
	}
}

void replaceAll(char *string, char oldString, char newString)
{
	unsigned int i;

	for (i = 0; i < strlen(string); i++)
		if (string[i] == oldString)
			string[i] = newString;
}

#ifdef HAVE_MOTIF
Arg args[10];

void updateToggle(Widget menu, Widget *menuItem, Boolean toggle,
		int number, XtCallbackProc callback)
{
	char button[18];

	(void) sprintf(button, "button_%d", number);
	if ((*menuItem = XtNameToWidget(menu, button)) != (Widget) 0) {
		if (toggle)
			XtVaSetValues(*menuItem,
				XmNset, toggle, NULL);
		XtAddCallback(*menuItem, XmNvalueChangedCallback, callback,
			(XtPointer) NULL);
	}
}

void updateRadio(Widget menu, int mode, int size,
		XtCallbackProc callback)
{
	Widget menuItem;
	char button[18];
	int i;

	for (i = 0; i < size; i++) {
		(void) sprintf(button, "button_%d", i);
		if ((menuItem = XtNameToWidget(menu, button)) != (Widget) 0
				&& mode == i) {
			XtVaSetValues(menuItem,
				XmNset, True, NULL);
			if (callback)
				XtAddCallback(menuItem,
					XmNvalueChangedCallback,
					callback,
					(XtPointer) NULL);
		} else
			XtVaSetValues(menuItem,
				XmNset, False, NULL);
	}
}

void setToggle(Widget w, Boolean state, Boolean radio)
{
#if XmVersion > 1002
	XmToggleButtonSetState(w, state, radio);
#else
	XtVaSetValues(w,
		XmNset, state, NULL);
#endif
}

void setRadio(Widget menu, int mode, int choices)
{
	updateRadio(menu, mode, choices, NULL);
}

void setChanger(Widget w, int value, Boolean spin)
{
	if (spin) {
#if XmVersion > 1002
		XtVaSetValues(w,
			XmNposition, value, NULL);
#else
		return;
#endif
	} else
		XmScaleSetValue(w, value);
}

void makePosVisible(Widget list_w, int item_no)
{
	int top, visible;

	XtVaGetValues(list_w,
		XmNtopItemPosition, &top,
		XmNvisibleItemCount, &visible, NULL);
	if (item_no < top) {
		XmListSetPos(list_w, item_no);
	} else if (item_no >= top + visible) {
		XmListSetBottomPos(list_w, item_no);
	}
}

Widget createQuery(Widget w, const char *title, char *text,
		XtCallbackProc callback)
{
	Widget button, messageBox;
	char titleDsp[TITLE_FILE_LENGTH];
	static XmStringCharSet charSet =
		(XmStringCharSet) XmSTRING_DEFAULT_CHARSET;
	XmString titleString = NULL, messageString = NULL;

	messageString = XmStringCreateLtoR(text, charSet);
	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	titleString = XmStringCreateSimple((char *) titleDsp);
	XtSetArg(args[0], XmNdialogTitle, titleString);
	XtSetArg(args[1], XmNmessageString, messageString);
	messageBox = XmCreateWarningDialog(w, (char *) "queryBox",
		args, 2);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(button);
	XmStringFree(titleString);
	XmStringFree(messageString);
	XtAddCallback(messageBox, XmNokCallback,
		(XtCallbackProc) callback, (XtPointer) NULL);
	return messageBox;
}

Widget createPause(Widget w, char *title, char *text, XtCallbackProc callback)
{
	Widget button, messageBox;
	char titleDsp[TITLE_FILE_LENGTH];
	static XmStringCharSet charSet =
		(XmStringCharSet) XmSTRING_DEFAULT_CHARSET;
	XmString titleString = NULL, messageString = NULL;
	XmString cancelString = XmStringCreateLocalized((char *) "Yes");
	XmString helpString = XmStringCreateLocalized((char *) "No");

	messageString = XmStringCreateLtoR(text, charSet);
	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	titleString = XmStringCreateSimple((char *) titleDsp);
	XtSetArg(args[0], XmNdialogTitle, titleString);
	XtSetArg(args[1], XmNmessageString, messageString);
	XtSetArg(args[2], XmNdefaultButtonType, XmDIALOG_HELP_BUTTON);
	XtSetArg(args[3], XmNcancelLabelString, cancelString);
	XtSetArg(args[4], XmNhelpLabelString, helpString);
	messageBox = XmCreateWarningDialog(w, (char *) "queryBox",
		args, 5);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_OK_BUTTON);
	XtUnmanageChild(button);
	XmStringFree(titleString);
	XmStringFree(messageString);
	return messageBox;
}

Widget createHelp(Widget w, const char *title, char *text)
{
	Widget button, messageBox;
	char titleDsp[TITLE_FILE_LENGTH];
	XmString titleString = NULL, messageString = NULL, buttonString = NULL;
	static XmStringCharSet charSet =
		(XmStringCharSet) XmSTRING_DEFAULT_CHARSET;

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	titleString = XmStringCreateSimple((char *) titleDsp);
	buttonString = XmStringCreateSimple((char *) "OK");
	messageString = XmStringCreateLtoR(text, charSet);
	XtSetArg(args[0], XmNdialogTitle, titleString);
	XtSetArg(args[1], XmNokLabelString, buttonString);
	XtSetArg(args[2], XmNmessageString, messageString);
	messageBox = XmCreateInformationDialog(w, (char *) "helpBox",
		args, 3);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_CANCEL_BUTTON);
	XtUnmanageChild(button);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(button);
	XmStringFree(titleString);
	XmStringFree(buttonString);
	XmStringFree(messageString);
	return messageBox;
}
/*http://www.ist.co.uk/motif/books/vol6A/ch-5.fm.html*/
Widget createScrollHelp(Widget w, char *title, char *text, Pixmap pixmap)
{
	Widget dialog, pane, scrolledText, form, label, button;
	int n = 0;
	char titleDsp[TITLE_FILE_LENGTH];
	XmString titleString = NULL;

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	titleString = XmStringCreateSimple((char *) titleDsp);
	XtSetArg(args[0], XmNdialogTitle, titleString);
	dialog = XmCreateMessageDialog(w, title, args, 1);
	pane = XtVaCreateWidget("pane", xmPanedWindowWidgetClass, dialog,
		XmNsashWidth, 1, XmNsashHeight, 1, NULL);
	form = XtVaCreateWidget("form", xmFormWidgetClass, pane, NULL);
	label = XtVaCreateManagedWidget("label", xmLabelGadgetClass, form,
		XmNlabelType, XmPIXMAP,
		XmNlabelPixmap, pixmap,
		XmNleftAttachment, XmATTACH_FORM,
		XmNtopAttachment, XmATTACH_FORM,
		XmNbottomAttachment, XmATTACH_FORM, NULL);
	XtSetArg(args[n], XmNdialogTitle, titleString); n++;
	XtSetArg(args[n], XmNscrollVertical, True); n++;
	XtSetArg(args[n], XmNscrollHorizontal, False); n++;
	XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
	XtSetArg(args[n], XmNeditable, False); n++;
	XtSetArg(args[n], XmNcursorPositionVisible, False); n++;
	XtSetArg(args[n], XmNwordWrap, False); n++;
	XtSetArg(args[n], XmNresizeWidth, True); n++;
	XtSetArg(args[n], XmNvalue, text); n++;
	XtSetArg(args[n], XmNrows, PAGE_SIZE); n++;
	scrolledText = XmCreateScrolledText(form, (char *) "helpText", args, n);
	XtVaSetValues(XtParent(scrolledText),
		XmNleftAttachment, XmATTACH_WIDGET,
		XmNleftWidget, label,
		XmNtopAttachment, XmATTACH_FORM,
		XmNrightAttachment, XmATTACH_FORM,
		XmNbottomAttachment, XmATTACH_FORM, NULL);
	XmStringFree(titleString);
	XtManageChild(scrolledText);
	XtManageChild(form);
	XtManageChild(pane);
	button = XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON);
	XtUnmanageChild(button);
	button = XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(button);
	return dialog;
}

#ifdef USE_SPIN
void createSpinner(Widget parent, Widget *spinner, char *label,
		int value, int min, int max, int size,
		XtCallbackProc callback)
{
	XtManageChild(XmCreateLabel(parent, (char *) label, NULL, 0));
	*spinner = XtVaCreateManagedWidget("spinner",
		xmSimpleSpinBoxWidgetClass, parent,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, min,
		XmNmaximumValue, max,
		XmNposition, value,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, size,
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(*spinner, XmNvalueChangedCallback,
		(XtCallbackProc) callback, (XtPointer) NULL);
}
#endif

void createSlider(Widget parent, Widget *slider, char *label,
		int value, int min, int max, int size, int width,
		XtCallbackProc callback)
{
	XtManageChild(XmCreateLabel(parent, (char *) label, NULL, 0));
	*slider = XtVaCreateManagedWidget("slider",
		xmScaleWidgetClass, parent,
		XmNminimum, min,
		XmNmaximum, max,
		XmNvalue, value,
		XmNscaleWidth, width,
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(*slider, XmNvalueChangedCallback,
		(XtCallbackProc) callback, (XtPointer) NULL);
}

void createRadio(Widget parent, Widget **radioModes,
		const char **list, const char *label, int num,
		XtCallbackProc callback)
{
	int i, len;
	Widget radioRowCol;

	len = strlen(label) + 1;
	(void) XtVaCreateManagedWidget("text",
		xmLabelWidgetClass, parent,
		XtVaTypedArg, XmNlabelString,
		XmRString, (char *) label, len, NULL);
	radioRowCol = XtVaCreateManagedWidget("radioRowCol",
		xmRowColumnWidgetClass, parent,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT,
		XmNradioBehavior, True, NULL);
	for (i = 0; i < num; i++) {
		radioModes[i] = (Widget *) XtVaCreateManagedWidget(list[i],
			xmToggleButtonGadgetClass, radioRowCol,
			XmNradioBehavior, True, NULL);
		XtAddCallback((Widget) radioModes[i], XmNvalueChangedCallback,
			(XtCallbackProc) callback,
			(XtPointer) ((size_t) i));
	}

}

extern void createScrollMenu(Widget parent, Widget *menu,
		const char **list, const char *label, int init, int num,
		XtCallbackProc menuCallback)
{
	XmString stringLabel;
	XmStringTable table;
	int i;

	table = (XmStringTable) XtMalloc (num * sizeof (XmString *));
	for (i = 0; i < num; i++) {
		table[i] = XmStringCreateSimple((char *) list[i]);
	}
	stringLabel = XmStringCreateSimple((char *) label);
	(void) XtVaCreateManagedWidget("menuLabel",
		xmLabelWidgetClass, parent,
		XmNlabelString, stringLabel, NULL);
	XmStringFree(stringLabel);
	*menu = XmCreateScrolledList(parent, (char *) "scrolledList",
		NULL, 0);
	if (num > 5)
		i = 5;
	else
		i = num;
	XtVaSetValues(*menu,
		XmNvisibleItemCount, i,
		XmNitemCount, num,
		XmNitems, table, NULL);
	XtManageChild(*menu);
	XtAddCallback(*menu, XmNbrowseSelectionCallback,
		(XtCallbackProc) menuCallback, (XtPointer) NULL);
	XmListSelectPos(*menu, init + 1, True);
	makePosVisible(*menu, init + 1);
	for (i = 0; i < num; i++)
		XmStringFree(table[i]);
	XtFree((char *) table);
}

#elif defined(HAVE_ATHENA)
Atom wmDeleteWindow = None;

#include "pixmaps/check.xbm"
#include "pixmaps/box.xbm"
#include "pixmaps/up.xbm"
#include "pixmaps/down.xbm"

void closePopupPanel(Widget w)
{
	XtPopdown(w);
}

void closePopupPanel2(Widget w)
{
	XtPopdown(XtParent(XtParent(w)));
}

void closePopupPanel3(Widget w)
{
	XtPopdown(XtParent(XtParent(XtParent(w))));
}

void deleteWindowProc(Widget w, XEvent *event, String *pars,
	Cardinal *nPars)
{
	exit(0);
}

void createBlank(char **string, int size, char *defaultString,
		int side)
{
	int i, defSize = 0, midSize = 0;
	char * chars;

	if (defaultString != NULL)
		defSize = strlen(defaultString);
	defSize++;
	if (size < defSize)
		size = defSize;
#if 0
	int offset = 0;

	if (side == 1)
		offset = 10;
	*string = (char *) malloc(size + offset);
#endif
	*string = (char *) malloc(size);
	chars = *string;
	if (side == 0) /* middle */
		midSize = (size - defSize) >> 1;
	else if (side == -1) /* right justified */
		midSize = size - defSize;
	for (i = 0; i < size - 1; i++) {
		if (i - midSize >= 0 &&
				(i - midSize < defSize - 1))
			chars[i] = defaultString[i - midSize];
		else
			chars[i] = ' ';
	}
#if 0
	for (i = 0; i < offset; i ++)
		chars[size - 1 + i] = ' ';
	chars[size - 1 + offset] = '\0';
#endif
	chars[size - 1] = '\0';
}

int findMaxLength(char **list, int num) {
	int i, temp, max;

	max = 0;
	for (i = 0; i < num; i++) {
		temp = strlen(list[i]);
		if (temp > max)
			max = temp;
	}
	return ++max;
}

void setLabel(Widget label, int value, int min, int max)
{
	char buff[BUFSIZ];

	/* negative side off by one on purpose, causes alignment issue */
	if (max >= 1000 || min < -100)
		(void) sprintf(buff, "%4d", value);
	else if (max >= 100 || min < -10)
		(void) sprintf(buff, "%3d", value);
	else if (max >= 10 || min < -1)
		(void) sprintf(buff, "%2d", value);
	else
		(void) sprintf(buff, "%1d", value);
	XtVaSetValues(label,
		XtNlabel, buff, NULL);
}

void checkBounds(int *value, int min, int *max, Boolean maxFixed)
{
	if (min >= *max)
		*max = min + 1;
	if (*value > *max) {
		if (maxFixed)
			*value = *max;
		else
			*max = *value;
	} else if (*value < min)
		*value = min;
}

void setScale(Widget slider, Widget label, int value, int min, int max,
	Boolean maxFixed)
{
	checkBounds(&value, min, &max, maxFixed);
	if (slider != NULL) {
		float percentage = ((float) (value - min)) / (max - min);
#ifdef DEBUG
		(void) printf("setScale: percentage=%g\n", percentage);
#endif
		XawScrollbarSetThumb(slider, percentage, 0.0);
	}
	setLabel(label, value, min, max);
}

void setScaleCheck(Widget slider, Widget label, int value, int min, int max,
	Boolean maxFixed)
{
	float old;
	int oldValue;

	XtVaGetValues(slider,
		XtNtopOfThumb, &old, NULL);
	oldValue = (int) (old * (max - min)) + min;
	if (oldValue == value) /* reduce flashing */
		return;
	setScale(slider, label, value, min, max, maxFixed);
}

void setToggle(Widget w, Boolean state, Boolean radio)
{
	XtVaSetValues(w,
		XtNstate, state, NULL);
}

void setRadio(Widget w, const char *choice)
{
	XtVaSetValues(w,
		XtNlabel, choice, NULL);
}

/* Seems this is not consistent with different versions of Athena */
Boolean thumbScroll(XtPointer callData, int *value,
		int min, int max, int width) {
	/* combining scroll and jump from Athena */
	long position = (size_t) callData; /* left or right button */
	*value = ABS(position) * (max - min) / width + min;
	if (*value < min || *value > max) { /* middle button */
		*value = (int) (*((float *) callData) * (max - min)) + min;
		if (*value < min || *value > max)
			return False;
	}
#ifdef DEBUG
	(void) printf("position=%ld, value=%d\n", position, *value);
#endif
	return True;
}

void menuCheck(Widget parent, Widget menuItem, Boolean value) {
	static Pixmap checkPixmap = None;

	if (checkPixmap == None)
		checkPixmap = XCreateBitmapFromData(XtDisplay(parent),
			RootWindow(XtDisplay(parent),
			DefaultScreen(XtDisplay(parent))),
			(char *)check_bits, check_width, check_height);
	XtVaSetValues(menuItem,
		XtNleftBitmap, ((value) ? checkPixmap : None), NULL);
}

void createDialog(Widget parent, Widget dialog)
{
	Position x, y;
	Dimension width, height;

	if (wmDeleteWindow == None)
		wmDeleteWindow = XInternAtom(XtDisplay(parent),
				"WM_DELETE_WINDOW", FALSE);
	XtVaGetValues(parent,
		XtNwidth, &width,
		XtNheight, &height, NULL);
	/*
	 * translate coordinates in application top-level window
	 * into coordinates from root window origin.
	 */
	XtTranslateCoords(parent,
		(Position) (width >> 1), /* x */
		(Position) (height >> 1), /* y */
		&x, &y); /* coords on root window */
	/* move popup shell to this position (it's not visible yet) */
	XtVaSetValues(dialog,
		XtNx, x,
		XtNy, y, NULL);
	XtPopup(dialog, XtGrabNone);
	XSetWMProtocols(XtDisplay(parent), XtWindow(dialog),
		&wmDeleteWindow, 1);
}

void createToggle(Widget parent, Widget *toggle,
		const char *label, int labelSize,
		Boolean state, XtCallbackProc callback)
{
	Widget name;
	Pixmap boxPixmap;
	char *labelName;

	createBlank(&labelName, labelSize, (char *) label, 1);
	name = XtVaCreateManagedWidget(labelName,
		labelWidgetClass, parent,
		XtNborderWidth, 0, NULL);
	free(labelName);
	boxPixmap = XCreateBitmapFromData(XtDisplay(parent),
		RootWindow(XtDisplay(parent),
		DefaultScreen(XtDisplay(parent))),
		(char *) box_bits, box_width, box_height);
	*toggle = XtVaCreateManagedWidget("",
		toggleWidgetClass, parent,
		XtNbitmap, boxPixmap,
		XtNfromHoriz, name, NULL);
	XtAddCallback(*toggle, XtNcallback, callback,
		(XtPointer) NULL);
	XtVaSetValues(*toggle,
		XtNstate, state, NULL);
}

void createSpinner(Widget parent, Widget *digits,
		const char *label, int labelSize,
		int value, int min, int max, Boolean maxFixed,
		XtCallbackProc upCallback, XtCallbackProc downCallback)
{
	Widget name, buttonsForm, upButton, downButton;
	Pixmap upPixmap, downPixmap;
	char *labelName;

	createBlank(&labelName, labelSize, (char *) label, 1);
	name = XtVaCreateManagedWidget(labelName,
		labelWidgetClass, parent,
		XtNborderWidth, 0, NULL);
	free(labelName);
	*digits = XtVaCreateManagedWidget("spinnerDigits",
		labelWidgetClass, parent,
		XtNfromHoriz, name, NULL);
	buttonsForm = XtVaCreateManagedWidget("buttonsForm",
		formWidgetClass, parent,
		XtNborderWidth, 0,
		XtNfromHoriz, digits, NULL);
	upPixmap = XCreateBitmapFromData(XtDisplay(parent),
		RootWindow(XtDisplay(parent),
		DefaultScreen(XtDisplay(parent))),
		(char *) up_bits, up_width, up_height);
	downPixmap = XCreateBitmapFromData(XtDisplay(parent),
		RootWindow(XtDisplay(parent),
		DefaultScreen(XtDisplay(parent))),
		(char *) down_bits, down_width, down_height);
	upButton = XtVaCreateManagedWidget("upButton",
		repeaterWidgetClass, buttonsForm,
		XtNvertDistance, 0,
		XtNbitmap, upPixmap, NULL);
	XtAddCallback(upButton, XtNcallback, upCallback,
		(XtPointer) digits);
	downButton = XtVaCreateManagedWidget("downButton",
		repeaterWidgetClass, buttonsForm,
		XtNvertDistance, 1,
		XtNbitmap, downPixmap,
		XtNfromVert, upButton, NULL);
	XtAddCallback(downButton, XtNcallback, downCallback,
		(XtPointer) digits);
	setScale(NULL, *digits, value, min, max, maxFixed);
}

void createSlider(Widget parent, Widget *digits, Widget *slider,
		const char *label, int labelSize,
		int value, int min, int max, Boolean maxFixed, int width,
		XtCallbackProc scrollCallback, XtCallbackProc jumpCallback)
{
	Widget name;
	char *labelName;

	createBlank(&labelName, labelSize, (char *) label, 1);
	name = XtVaCreateManagedWidget(labelName,
		labelWidgetClass, parent,
		XtNborderWidth, 0, NULL);
	free(labelName);
	*digits = XtVaCreateManagedWidget("label",
		labelWidgetClass, parent,
		XtNfromHoriz, name, NULL);
	*slider = XtVaCreateManagedWidget("slider",
		scrollbarWidgetClass, parent,
		XtNorientation, XtorientHorizontal,
		XtNlength, width, NULL);
	XtAddCallback(*slider, XtNscrollProc, scrollCallback,
		(XtPointer) *digits);
	XtAddCallback(*slider, XtNjumpProc, jumpCallback,
		(XtPointer) *digits);
	setScale(*slider, *digits, value, min, max, maxFixed);
}

void createRadio(Widget parent, Widget **radioModes,
		const char **list, const char *label, int labelSize,
		int init, int num,
		XtCallbackProc toggleCallback)
{
	Widget name, toggle, firstToggle = None;
	int i;
	char *defaultString;

	createBlank(&defaultString, labelSize, (char *) label, 1);
	name = XtVaCreateManagedWidget(defaultString,
		labelWidgetClass, parent,
		XtNborderWidth, 0, NULL);
	free(defaultString);
	toggle = name;
	for (i = 0; i < num; i++) {
		toggle = XtVaCreateManagedWidget(list[i],
			toggleWidgetClass, parent,
			XtNradioGroup, firstToggle,
			XtNfromHoriz, toggle, NULL);
		radioModes[i] = (Widget *) toggle;
		if (firstToggle == None) {
			firstToggle = toggle;
		}
		if (i == init) {
			XtVaSetValues(toggle,
				XtNstate, True, NULL);
		}
		XtAddCallback(toggle, XtNcallback,
			(XtCallbackProc) toggleCallback, (XtPointer) (size_t) i);
	}
}

void createPopupMenu(Widget parent, Widget *choice,
		const char **list, const char *label, int labelSize,
		int init, int num,
		XtCallbackProc menuCallback) {
	Widget name, menu, menuItem;
	int i, max;
	char *defaultString;

	createBlank(&defaultString, labelSize, (char *) label, 1);
	name = XtVaCreateManagedWidget(defaultString,
		labelWidgetClass, parent,
		XtNborderWidth, 0, NULL);
	free(defaultString);
	max = findMaxLength((char **) list, num);
	createBlank(&defaultString, max, (char *) list[init], 0);
	*choice = XtVaCreateManagedWidget(defaultString,
		menuButtonWidgetClass, parent,
		XtNfromHoriz, name,
		/*XtNwidth, 150,*/
		NULL);
	free(defaultString);
	menu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, *choice, NULL);
	for (i = 0; i < num; i++) {
		menuItem = XtVaCreateManagedWidget(list[i],
			smeBSBObjectClass, menu, NULL);
		XtAddCallback(menuItem, XtNcallback,
			(XtCallbackProc) menuCallback, (XtPointer) (size_t) i);
	}
}

void createMenu(Widget parent, Widget *menuLabel, Widget previous,
		const char **list, const char *label, int num,
		unsigned long lineMask, Boolean check,
		XtCallbackProc menuCallback) {
	Widget menu, menuItem;
	int i;

	if (previous == NULL)
		*menuLabel = XtVaCreateManagedWidget(label,
			menuButtonWidgetClass, parent,
			XtNborderWidth, 0, NULL);
	else
		*menuLabel = XtVaCreateManagedWidget(label,
			menuButtonWidgetClass, parent,
			XtNborderWidth, 0,
			XtNfromHoriz, previous, NULL);
	menu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, *menuLabel, NULL);
	for (i = 0; i < num; i++) {
		if (check)
			menuItem = XtVaCreateManagedWidget(list[i],
				smeBSBObjectClass, menu,
				XtNleftMargin, check_width + 4, NULL);
		else
			menuItem = XtVaCreateManagedWidget(list[i],
				smeBSBObjectClass, menu, NULL);
		if (lineMask & 1)
			(void) XtVaCreateManagedWidget("menuButton",
				smeLineObjectClass, menu, NULL);
		lineMask = lineMask >> 1;
		XtAddCallback(menuItem,
			XtNcallback, (XtCallbackProc) menuCallback,
			(XtPointer) (size_t) i);
	}
}

Widget createQuery(Widget w, const char *title, char *text,
		XtCallbackProc callback) {
	return NULL;
}

void createHelp(Widget parent, Widget *dialogShell, const char *title,
		char *text, XtCallbackProc callback)
{
	Widget dialog, button;
	char titleDsp[TITLE_FILE_LENGTH];

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	*dialogShell = XtCreatePopupShell(titleDsp,
		transientShellWidgetClass, parent, NULL, 0);
	dialog = XtVaCreateManagedWidget("dialog",
		dialogWidgetClass, *dialogShell,
		XtNlabel, text, NULL);
	button = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(button, XtNcallback,
		callback, dialog);
	XtRealizeWidget(*dialogShell);
	XSetWMProtocols(XtDisplay(parent),
		XtWindow(*dialogShell), &wmDeleteWindow, 1);
}

void createScrollHelp(Widget parent, Widget *dialogShell, const char *title,
		char *text, XtCallbackProc callback)
{
	Widget dialog, scroll, button;
	char titleDsp[TITLE_FILE_LENGTH];

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	*dialogShell = XtCreatePopupShell(titleDsp,
		transientShellWidgetClass, parent, NULL, 0);
	dialog = XtVaCreateManagedWidget("dialog",
		formWidgetClass, *dialogShell, NULL);
	scroll = XtVaCreateManagedWidget("text",
		asciiTextWidgetClass, dialog,
		XtNwidth, 105 * 8,
		XtNheight, 512,
		/*XtNscrollVertical, XawtextScrollWhenNeeded,*/
		XtNscrollVertical, XawtextScrollAlways,
		XtNstring, text, NULL);
	button = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog,
		XtNfromVert, scroll, NULL);
	XtAddCallback(button, XtNcallback,
		callback, dialog);
	XtRealizeWidget(*dialogShell);
	XSetWMProtocols(XtDisplay(parent),
		XtWindow(*dialogShell), &wmDeleteWindow, 1);
}
#endif
