/*
 * Copyright (c) 2004 Stefan Ulrich
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 */

/*
 * Panel 3 (Helper Applications) for xdvik preferences dialog.
 */

#include "xdvi-config.h"
#include "xdvi.h"

#include "x_util.h"
#include "xm_colorsel.h"
#include "topic-window.h"
#include "util.h"
#include "string-utils.h"
#include "message-window.h"
#include "events.h"
#include "xm_menu.h"

#include "xm_prefsP.h"
#include "xm_prefs.h"
#include "xm_prefs_helpers.h"

#ifdef MOTIF /* entire file */

#include <X11/Xatom.h>

#include <Xm/Xm.h>
#include <Xm/Protocols.h>
#include <Xm/DialogS.h>
#include <Xm/LabelG.h>
#include <Xm/PushB.h>
#include <Xm/Form.h>
#include <Xm/Frame.h>
#include <Xm/PanedW.h>
#include <Xm/DrawnB.h>
#include <Xm/RowColumn.h>
#include <Xm/Scale.h>
#include <Xm/ToggleBG.h>
#include <Xm/PushBG.h>
#include <Xm/Text.h>
#include <Xm/TextF.h>
#include <Xm/FileSB.h>
#include <Xm/SelectioB.h>

struct choice_dialog_info {
    struct topic_info *tinfo;
    /*     struct prefs_choice *prefs; */
    Widget combo_box;
    Widget message_popup;
};

static char **m_browser_list = NULL;
static char **m_editor_list = NULL;

/*     "mozilla -remote 'openURL(%s,new-window)'", */
/*     "netscape -raise -remote 'openURL(%s,new-window)'", */
/*     "xterm -e lynx %s", */
/*     Xdvi_ADD_COMMAND_STR, */
/*     NULL /\* terminate *\/ */
/* }; */

/* static const char *editor_list[] = { */
/*     "gnuclient -q +%l %f", */
/*     "emacsclient --no-wait +%l %f", */
/*     "gvim --servername xdvi --remote +%l %f", */
/*     "nc -noask +%l %f", */
/*     "xterm -e vi +%l %f", */
/*     Xdvi_ADD_COMMAND_STR, */
/*     NULL /\* terminate *\/ */
/* }; */

/* combo box only available with Motif 2.0;
 * the lesstif version is incomplete
 */
#if XmVersion >= 2000 && !defined(LESSTIF_VERSION)
#include <Xm/ComboBox.h>
#include <Xm/List.h>
#define USE_COMBOBOX 1
#else
#include <Xm/CascadeBG.h>
#define USE_COMBOBOX 0
#endif

static void select_browser_cb(Widget w, XtPointer client_data, XtPointer call_data);
static void select_editor_cb(Widget w, XtPointer client_data, XtPointer call_data);

/*
 * Helper functions
 */
static char *
flatten_list(char **list, const char *sep)
{
    char *result = xstrdup("");
    int i;
    
    for (i = 0; list[i] != NULL; i++) {
	result = xstrcat(result, list[i]);
	result = xstrcat(result, sep);
    }
    return result;
}

static char **
add_to_list(char **list, const char *str)
{
    int i, k;
    /* reallocate with larger capacity */
    for (i = 0; list[i] != NULL; i++) { ; }
    i++;
    list = xrealloc(list, (i + 1) * sizeof *list);

    /* shift old contents down */
    for (k = i; k > 0; k--) {
	list[k] = list[k - 1];
    }
    /* add new element at beginning */
    list[0] = xstrdup(str);

    return list;
}


/*
 * User clicked on `Help' in browser text input prompt
 */
static void
help_browser_dialog_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
    Widget popup = popup_message(XtParent(w),
				 MSG_HELP,
				 NULL,
				 "The browser is used by xdvi to browse remote documents. "
				 "Please enter the name of the browser (i.e. the executable program) "
				 "you want to use. The browser command may optionally contain a string "
				 "`%%s' which is replaced by the current URL. If no `%%s' is present, "
				 "the URL argument is simply appended to the command.");
    UNUSED(client_data);
    UNUSED(call_data);

    TRACE_GUI((stderr, "setting user data to %p", (void *)popup));
    XtVaSetValues(w, XmNuserData, popup, NULL);
}

/*
 * User clicked on `Help' in editor text input prompt
 */
static void
help_editor_dialog_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
    Widget popup = popup_message(XtParent(w),
				 MSG_HELP,
				 NULL,
				 "The editor is used by reverse search to open the TeX source for a DVI file "
				 "(see `Help' -> `Source Specials' for details)."
				 "Please enter the name of the editor executable you want to use. "
				 "The editor command may optionally contain two format strings: "
				 "`%%l' is replaced by the line number in the TeX file, and `%%f' by the file name. "
				 "(If the format strings are not present, they are appended implicitly.)");
    UNUSED(client_data);
    UNUSED(call_data);

    TRACE_GUI((stderr, "setting user data to %p", (void *)popup));
    XtVaSetValues(w, XmNuserData, popup, NULL);
}

/*
 * User cancelled browser or editor text input prompt
 */
static void
destroy_dialog_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
/*     Widget browser_combo = (Widget)client_data; */
    struct choice_dialog_info *info = (struct choice_dialog_info *)client_data;
    Widget browser_combo = NULL;
    Widget help_popup;
#if USE_COMBOBOX
    Widget browser_list_w;
#endif
    UNUSED(call_data);

    XtVaGetValues(w, XmNuserData, &help_popup, NULL);
    TRACE_GUI((stderr, "GOT help_popup: %p", (void *)help_popup));

    if (info != NULL)
	browser_combo = info->combo_box;
    
    if (browser_combo != NULL) {
#if USE_COMBOBOX
	/* Need to unselect the `Other...' entry from the list; to do this,
	   we select the index from XmNuserData if it's >= 0, or the first item. */
	int idx;
	XtVaGetValues(browser_combo,
		      XmNlist, &browser_list_w,
		      XmNuserData, &idx,
		      NULL);
	if (browser_list_w == 0)
	    XDVI_ERROR((stderr, "couldn't get list component of combo box!\n"));
	else {
	    if (idx < 0)
		idx = 0;
	    XmListSelectPos(browser_list_w, idx + 1, True);
	}
#else
	Widget parent;
	if ((parent = XtParent(w)) != NULL) {
	    Widget rowcol, child;
	    if (strcmp(XtName(parent), Xdvi_BROWSER_POPUP) == 0) {
		if (get_widget_by_name(&rowcol, globals.widgets.top_level, Xdvi_BROWSER_COMBO_NAME, True)) {
		    XtVaGetValues(rowcol, XmNuserData, &child, NULL);
		    XtVaSetValues(rowcol, XmNmenuHistory, child, NULL);
		}
	    }
	    else if (strcmp(XtName(parent), Xdvi_EDITOR_POPUP) == 0) {
		if (get_widget_by_name(&rowcol, globals.widgets.top_level, Xdvi_EDITOR_COMBO_NAME, True)) {
		    XtVaGetValues(rowcol, XmNuserData, &child, NULL);
		    XtVaSetValues(rowcol, XmNmenuHistory, child, NULL);
		}
	    }
	    else {
		XDVI_WARNING((stderr, "unexpected widget name `%s' in destroy_dialog_cb", XtName(parent)));
	    }
	}
#endif
    }

    /*
     * Also popdown dependent help window if it exists before destroying the widget;
     * otherwise, Motif may even crash in _XmIsFastSubclass() when the help window is moved!
     * 
     * Since `help_popup' is the top-level xmDialogShellWidget, we need to get its
     * xmMessageBoxWidget child (by name ...). Don't report an error if it doesn't
     * exist in case the help window has already been closed.
    */
    if (help_popup != NULL) {
	static Widget message;
	if (get_widget_by_name(&message, help_popup, Xdvi_MESSAGE_DIALOG_NAME, False))
	    XtCallCallbacks(message, XmNokCallback, NULL);
    }

    if (info != NULL) {
	struct prefs_choice *prefs = (struct prefs_choice *)info->tinfo->data;
	remove_from_deplist(prefs, w);
	free(info);
    }
    
    /* destroy the parent of this dialog (the shell) */
    XtDestroyWidget(XtParent(w));
}

static void
h_get_input_wrapper(const char *listbox_name,
		    Widget w, XtPointer client_data, XtPointer call_data)
{
/*     Widget combobox = (Widget)client_data; */
    struct choice_dialog_info *info = (struct choice_dialog_info *)client_data;
    struct topic_info *tinfo = info->tinfo;
    struct prefs_choice *prefs = (struct prefs_choice *)tinfo->data;
    Widget combobox, child;
    /* following need to be allocated since we want to set resource.xyz to it */
    static char *browser_choice= NULL, *editor_choice = NULL;
#if !USE_COMBOBOX
    XtCallbackProc select_cb = NULL;
#endif
    XmSelectionBoxCallbackStruct *cbs = (XmSelectionBoxCallbackStruct *)call_data;
    static char *ptr = NULL;
    char *tmp_list;

    if (call_data == NULL) /* widget already being destroyed? */
	return;

    ASSERT(info != NULL, "client_data in h_get_input_wrapper mustn't be NULL!");
    combobox = info->combo_box;
    child = info->message_popup;
    XtVaGetValues(w, XmNuserData, &child, NULL);
    TRACE_GUI((stderr, "GOT child: %p", (void *)child));

    if (ptr)
	XtFree((XtPointer)ptr);
    ptr = NULL;
    XmStringGetLtoR(cbs->value, G_charset, &ptr);
    if (strlen(ptr) == 0 || is_spaces_only(ptr)) {
	popup_message(XtParent(w),
		      MSG_ERR,
		      NULL,
		      "Empty input string");
	return;
    }

    if (strcmp(listbox_name, Xdvi_BROWSER_COMBO_NAME) == 0) {
#if 0
	int i;
#endif
	
	free(browser_choice);
	browser_choice = xstrdup(ptr);
	resource.browser = browser_choice;

#if 0
	for (i = 0; m_browser_list[i] != NULL; i++) {
	    fprintf(stderr, "list %d: |%s|\n", i, m_browser_list[i]);
	}
#endif
	m_browser_list = add_to_list(m_browser_list, ptr);
#if 0
	for (i = 0; m_browser_list[i] != NULL; i++) {
	    fprintf(stderr, "NEW list %d: |%s|\n", i, m_browser_list[i]);
	}
#endif
	tmp_list = flatten_list(m_browser_list, "\n");
#if 0
	fprintf(stderr, "TMP LIST: |%s|\n", tmp_list);
#endif /* 0 */
	store_preference(&(prefs->db), "prefsBrowserList", "%s", tmp_list);
	free(tmp_list);
	
	store_preference(&(prefs->db), "wwwBrowser", "%s", ptr);
#if !USE_COMBOBOX
	select_cb = select_browser_cb;
#endif
    }
    else if (strcmp(listbox_name, Xdvi_EDITOR_COMBO_NAME) == 0) {
	free(editor_choice);
	editor_choice = xstrdup(ptr);
	resource.editor = editor_choice;

	m_editor_list = add_to_list(m_editor_list, ptr);
	tmp_list = flatten_list(m_editor_list, "\n");
	store_preference(&(prefs->db), "prefsEditorList", "%s", tmp_list);
	free(tmp_list);
	
	store_preference(&(prefs->db), "editor", "%s", ptr);
#if !USE_COMBOBOX
	select_cb = select_editor_cb;
#endif
    }
    else
	XDVI_WARNING((stderr, "Unexpected name in h_get_input_wrapper: `%s'", listbox_name));

#if USE_COMBOBOX
    UNUSED(listbox_name);
    /* add user input to the combo box list, and make it current */
    XmComboBoxAddItem(combobox, cbs->value, 1, True);
    XmComboBoxSelectItem(combobox, cbs->value);
#else
    {
	Widget new_menu, parent, grandparent, rowcol;
	if ((parent = XtParent(w)) != NULL && (grandparent = XtParent(parent)) != NULL) {
	    /* add new item to front of list ... */
	    new_menu = XtVaCreateManagedWidget(ptr, xmPushButtonGadgetClass, grandparent,
					       XmNpositionIndex, 0,
					       XmNuserData, tinfo,
					       NULL);
	    if (select_cb != NULL) {
		XtAddCallback(new_menu, XmNactivateCallback, select_cb, grandparent);
	    }
	    /* ... and make it current! */
	    if (get_widget_by_name(&rowcol, globals.widgets.top_level, listbox_name, True)) {
		XtVaSetValues(rowcol, XmNmenuHistory, new_menu, NULL);
	    }
	}
    }
#endif

    /* as above: popdown help window */
    if (child != NULL) {
	Widget message;
	if (get_widget_by_name(&message, child, Xdvi_MESSAGE_DIALOG_NAME, False))
	    XtCallCallbacks(message, XmNokCallback, NULL);
    }

    remove_from_deplist(prefs, w);
    free(info);
    
    /* destroy the parent of this dialog (the shell) */
    XtDestroyWidget(XtParent(w));
}

static void
h_selector(const char *prompt_name,
	   const char *title_str, const char *label_str,
	   XtCallbackProc ok_cb, 
	   XtCallbackProc destroy_cb, 
	   XtCallbackProc help_cb, 
	   Widget w, XtPointer client_data, XtPointer call_data)
{
    struct topic_info *tinfo = NULL;
    struct prefs_choice *prefs = NULL;
    struct choice_dialog_info *info = NULL;
    char *choice;
    /* following need to be allocated since we want to set resource.xyz to it */
    static char *browser_choice= NULL, *editor_choice = NULL;

#if USE_COMBOBOX
    XmComboBoxCallbackStruct *cb;
#else
    XmString str;
    UNUSED(client_data);
    UNUSED(call_data);
#endif
    
#if USE_COMBOBOX
    tinfo = (struct topic_info *)client_data;
    prefs = (struct prefs_choice *)tinfo->data;
#endif
    info = xmalloc(sizeof *info);
#if USE_COMBOBOX
    cb = (XmComboBoxCallbackStruct *)call_data;

    if (cb->event == NULL) /* only browsing, no selection */
	return;
    
    choice = (char *)XmStringUnparse(cb->item_or_text, XmFONTLIST_DEFAULT_TAG,
				     XmCHARSET_TEXT, XmCHARSET_TEXT,
				     NULL, 0,
				     XmOUTPUT_ALL);
#else
    XtVaGetValues(w, XmNlabelString, &str, NULL);
    XmStringGetLtoR(str, G_charset, &choice);
    
    XtVaGetValues(w, XmNuserData, &tinfo, NULL);
    ASSERT(tinfo != NULL, "XmNuserData in callback musn't be NULL!");
    prefs = (struct prefs_choice *)tinfo->data;
#endif

    if (strcmp(choice, Xdvi_ADD_COMMAND_STR) == 0) {
	Widget prompt_widget;
	Arg args[8];
	int n = 0;
	XmString title = XmStringCreateLocalized((char *)title_str);
	XmString label = XmStringCreateLocalized((char *)label_str);
	
	XtSetArg(args[n], XmNselectionLabelString, label); n++;
	XtSetArg(args[n], XmNautoUnmanage, False); n++;
	XtSetArg(args[n], XmNdialogTitle, title); n++;
	XtSetArg(args[n], XmNuserData, NULL); n++;
	prompt_widget = XmCreatePromptDialog(
#if USE_COMBOBOX
					     w,
#else
					     XtParent(w),
#endif
					     (char *)prompt_name, args, n);
/* 	XmStringFree(label); */
	
	add_to_deplist(prefs, prompt_widget);
	
	info->tinfo = tinfo;
	info->combo_box = w;
	info->message_popup = NULL;
	
	/* Note: w is the browser_combo widget */
	XtAddCallback(prompt_widget, XmNokCallback, ok_cb, (XtPointer)info);
	XtAddCallback(prompt_widget, XmNcancelCallback, destroy_cb, (XtPointer)info);
	XtAddCallback(prompt_widget, XmNhelpCallback, help_cb, (XtPointer)info);
	
	XtManageChild(prompt_widget);
    }
    else { /* normal item */
#if USE_COMBOBOX
	int i;
#endif

	if (strcmp(prompt_name, Xdvi_BROWSER_POPUP_NAME) == 0) {
#if !USE_COMBOBOX
	    Widget rowcol;
	    if (get_widget_by_name(&rowcol, globals.widgets.top_level, Xdvi_BROWSER_COMBO_NAME, True)) {
		XtVaSetValues(rowcol, XmNuserData, w, NULL);
	    }
#endif
	    free(browser_choice);
	    browser_choice = xstrdup(choice);
	    resource.browser = browser_choice;
	    store_preference(&(prefs->db), "wwwBrowser", "%s", browser_choice);
	}
	else if (strcmp(prompt_name, Xdvi_EDITOR_POPUP_NAME) == 0) {
#if !USE_COMBOBOX
	    Widget rowcol;
	    if (get_widget_by_name(&rowcol, globals.widgets.top_level, Xdvi_EDITOR_COMBO_NAME, True)) {
		XtVaSetValues(rowcol, XmNuserData, w, NULL);
	    }
#endif
	    free(editor_choice);
	    editor_choice = xstrdup(choice);
	    resource.editor = editor_choice;
	    store_preference(&(prefs->db), "editor", "%s", editor_choice);
	}
	else
	    XDVI_ERROR((stderr, "Unknown category `%s' in h_selector()!", prompt_name));


#if USE_COMBOBOX
	/* update the currently selected value */
	XtVaGetValues(w, XmNselectedPosition, &i, NULL);
	XtVaSetValues(w, XmNuserData, i, NULL);
#endif
    }
#if USE_COMBOBOX
    XtFree(choice);
#endif
}

/*
 * User OK'ed browser text input prompt
 */
static void
get_browser_text_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
    if (XtIsRealized(w)) {
	h_get_input_wrapper(Xdvi_BROWSER_COMBO_NAME, w, client_data, call_data);
    }
}


/*
 * User OK'ed editor text input prompt
 */
static void
get_editor_text_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
    if (XtIsRealized(w)) {
	h_get_input_wrapper(Xdvi_EDITOR_COMBO_NAME, w, client_data, call_data);
    }
}


/*
 * User selected an item from browser combo box pulldown list
 */
static void
select_browser_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
    h_selector(Xdvi_BROWSER_POPUP_NAME,
	       "Xdvi: Add Browser Command",
	       "Browser Command (optional `%s' is replaced by URL): ",
	       get_browser_text_cb, destroy_dialog_cb, help_browser_dialog_cb,
	       w, client_data, call_data);
}

/*
 * User selected an item from editor combo box pulldown list
 */
static void
select_editor_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
    UNUSED(client_data);
    h_selector(Xdvi_EDITOR_POPUP_NAME,
	       "Xdvi: Add Editor Command",
	       "Editor Command (optional: `%l' = line number, `%f' = file name): ",
	       get_editor_text_cb, destroy_dialog_cb, help_editor_dialog_cb,
	       w, client_data, call_data);
}

void
update_preferences_helpers(void)
{
    Widget shell, browser_cascade, editor_cascade;

    if (get_widget_by_name(&shell, globals.widgets.top_level, Xdvi_PREFS_DIALOG_NAME, False)
	&& get_widget_by_name(&browser_cascade, shell, Xdvi_BROWSER_COMBO_NAME, True)
	&& get_widget_by_name(&editor_cascade, shell, Xdvi_EDITOR_COMBO_NAME, True)) {

#if USE_COMBOBOX

	Widget browser_list_w, editor_list_w;
	XmString str;
	int i;
	Boolean found = False;
	
	XtVaGetValues(browser_cascade, XmNlist, &browser_list_w, NULL);
	XtVaGetValues(editor_cascade, XmNlist, &editor_list_w, NULL);

	for (i = 0; m_browser_list[i] != NULL; i++) {
	    if (globals.curr_browser != NULL) { /* if this is set, ignore browser setting */
		if (strcmp(globals.curr_browser, m_browser_list[i]) == 0) {
		    found = True;
		    break;
		}
	    }
	    else if (resource.browser != NULL) {
		if (strcmp(resource.browser, m_browser_list[i]) == 0) {
		    found = True;
		    break;
		}
	    }
	}
	if (!found)
	    i = 0;
	str = XmStringCreateLtoR((char *)m_browser_list[i], "UNMARKED");
	XmComboBoxSelectItem(browser_cascade, str);
	XmStringFree(str);
	
	for (i = 0; m_editor_list[i] != NULL; i++) {
	    if (globals.curr_editor != NULL) { /* if this is set, ignore editor setting */
		if (strcmp(globals.curr_editor, m_editor_list[i]) == 0) {
		    found = True;
		    break;
		}
	    }
	    else if (resource.editor != NULL) {
		if (strcmp(resource.editor, m_editor_list[i]) == 0) {
		    found = True;
		    break;
		}
	    }
	}
	if (!found)
	    i = 0;
	str = XmStringCreateLtoR((char *)m_editor_list[i], "UNMARKED");
	XmComboBoxSelectItem(editor_cascade, str);
	XmStringFree(str);
	/* 	XmListSetPos(editor_list_w, i); */
	/* 	fprintf(stderr, "setting editor list index %d, %s\n", */
	/* 		i, editor_list[i-1]); */

	
	/* 	if (XmListGetSelectedPos(browser_list_w, &browser_items, &browser_cnt) */
	/* 	    && XmListGetSelectedPos(editor_list_w, &editor_items, &editor_cnt)) { */
	/* 	    if (browser_cnt > 0 && editor_cnt > 0) { */
	/* 		int browser_idx = browser_items[0] - 1; */
	/* 		int editor_idx = editor_items[0] - 1; */
	/* 		fprintf(stderr, "selected: %s, %s\n", */
	/* 			browser_list[browser_idx], */
	/* 			editor_list[editor_idx]); */
	/* 	    } */
	/* 	    else { */
	/* 		XDVI_WARNING((stderr, "Shouldn't happen: No items selected in browser list?")); */
	/* 		return; */
	/* 	    } */
	/* 	    XtFree((XtPointer)browser_items); */
	/* 	    XtFree((XtPointer)editor_items); */
	/* 	} */
#else
	Widget browser_menu, editor_menu;
	if (get_widget_by_name(&browser_menu, shell, Xdvi_BROWSER_MENU_NAME, True)
	    && get_widget_by_name(&editor_menu, shell, Xdvi_EDITOR_MENU_NAME, True)) {
	    int i, num_buttons;
	    WidgetList buttons;
	    
	    XtVaGetValues(browser_menu,
			  XmNnumChildren, &num_buttons,
			  XmNchildren, &buttons,
			  NULL);
	    for (i = 0; i < num_buttons; i++) {
		XmString str;
		char *ptr;
		
		XtVaGetValues(buttons[i], XmNlabelString, &str, NULL);
		XmStringGetLtoR(str, G_charset, &ptr);
/* 		fprintf(stderr, "kid %d: %s\n", i, ptr); */
		if (globals.curr_browser != NULL) { /* if this is set, ignore browser setting */
		    if (strcmp(globals.curr_browser, ptr) == 0) {
			break;
		    }
		}
		else if (resource.browser != NULL) {
		    if (strcmp(resource.browser, ptr) == 0) {
			break;
		    }
		}
	    }
	    if (i >= num_buttons) /* not found */
		i = 0;
	    XtVaSetValues(browser_cascade, XmNmenuHistory, buttons[i], NULL);

	    /* same for editor */
	    XtVaGetValues(editor_menu,
			  XmNnumChildren, &num_buttons,
			  XmNchildren, &buttons, NULL);
	    for (i = 0; i < num_buttons; i++) {
		XmString str;
		char *ptr;
		
		XtVaGetValues(buttons[i], XmNlabelString, &str, NULL);
		XmStringGetLtoR(str, G_charset, &ptr);
/* 		fprintf(stderr, "kid %d: %s\n", i, ptr); */
		if (globals.curr_editor != NULL) { /* if this is set, ignore editor setting */
		    if (strcmp(globals.curr_editor, ptr) == 0) {
			break;
		    }
		}
		else if (resource.editor != NULL) {
		    if (strcmp(resource.editor, ptr) == 0) {
			break;
		    }
		}
	    }
	    if (i >= num_buttons) /* not found */
		i = 0;
	    XtVaSetValues(editor_cascade, XmNmenuHistory, buttons[i], NULL);

	}
#endif
        /* for browser/editor, try:

       Widget      menu;
       int         num_buttons;
       WidgetList  buttons;
       
       XtVaGetValues( simple_option_widget, XmNsubMenuId, &menu, NULL);
       
       XtVaGetValues( menu, XmNnumChildren, &num_buttons,
       XmNchildren, &buttons, NULL ) ;
	
     */
    }
}

static Widget
h_create_command(const char *name,
		 const char *menu_name,
		 const char *label,
		 const char *curr_value,
		 Widget parent, Widget top,
		 char **command_list,
		 XtCallbackProc select_cb,
		 struct topic_info *tinfo)
{
    const char *ptr = NULL;
    size_t i;

#if USE_COMBOBOX
    XmStringTable str_list;
    Widget text_label, combo_box;
    size_t k, num;
    int curr_index = -1;
#else
    Widget menu, cascade, item;
    XmString str;
    Arg args[8];
    int n;
#endif
    
    /* check if we need to add the default resource name */
    ptr = curr_value;

    /* don't add it if it's already in our list */
    for (i = 0; ptr != NULL && command_list[i] != NULL; i++) {
	if (strcmp(command_list[i], ptr) == 0) {
	    ptr = NULL;
	}
    }
#if USE_COMBOBOX
    UNUSED(menu_name);
    text_label = XmCreateLabelGadget(parent, (char *)label, NULL, 0);
    XtVaSetValues(text_label,
		  XmNtopAttachment, XmATTACH_WIDGET,
		  XmNtopWidget, top,
		  XmNleftAttachment, XmATTACH_FORM,
/* 		  XmNrightAttachment, XmATTACH_FORM, */
		  NULL);

    /* count elements in command_list */
    for (num = 0; command_list[num] != NULL; num++) { ; }

    if (ptr != NULL)
	num++;
	
    str_list = (XmStringTable)XtMalloc(num * sizeof(XmString *));

    i = 0;
    if (ptr != NULL)
	str_list[i++] = XmStringCreateLtoR((char *)ptr, "UNMARKED");
    
    for (k = 0; i < num; i++, k++) {
	if (curr_value != NULL && strcmp(command_list[k], curr_value) == 0) {
	    curr_index = i;
	}
	if (strcmp(command_list[k], Xdvi_ADD_COMMAND_STR) == 0)
	    str_list[i] = XmStringCreateLtoR((char *)command_list[k], "MARKED");
	else
	    str_list[i] = XmStringCreateLtoR((char *)command_list[k], "UNMARKED");
    }
    combo_box = XtVaCreateWidget(name, xmComboBoxWidgetClass,
				 parent,
				 XmNtopAttachment, XmATTACH_WIDGET,
				 XmNtopWidget, top, /* if top == NULL, this used XmATTACH_FORM */
				 XmNleftAttachment, XmATTACH_WIDGET,
				 XmNleftWidget, text_label,
				 XmNrightAttachment, XmATTACH_FORM,
				 XmNcomboBoxType, XmDROP_DOWN_LIST,
				 XmNitems, str_list,
				 XmNitemCount, num,
				 XmNuserData, curr_index,
				 NULL);
    if (top != NULL) /* FIXME: This assumes we only have 2 items ... */
	XtVaSetValues(combo_box,
		      XmNbottomAttachment, XmATTACH_FORM,
		      NULL);
    
    /* make resource setting current value */
    if (curr_index >= 0) {
	XmComboBoxSelectItem(combo_box, str_list[curr_index]);
    }
	
    for (i = 0; i < num; i++)
	XmStringFree(str_list[i]);
    XtFree((XtPointer)str_list);

    XtAddCallback(combo_box, XmNselectionCallback, select_cb, (XtPointer)tinfo); 
    
    /*
     * workaround for pointer grabbing bug (see xm_menu.c). We need to use
     * the popdownCallback of the internal `GrabShell' child of the combo box.
     */
    {
	Widget grab_shell;
	if (get_widget_by_name(&grab_shell, combo_box, "GrabShell", True))
	    XtAddCallback(grab_shell, XtNpopdownCallback, popdown_callback, NULL);
    }
    adjust_heights(text_label, combo_box, NULL);

    XtManageChild(text_label);
    XtManageChild(combo_box);

    return combo_box;
#else
    menu = XmCreatePulldownMenu(parent, (char *)menu_name, NULL, 0);
/*     XtVaSetValues(menu, */
/* 		  XmNtopAttachment, XmATTACH_WIDGET, */
/* 		  XmNtopWidget, top, */
/* 		  XmNleftAttachment, XmATTACH_FORM, */
/* 		  XmNrightAttachment, XmATTACH_FORM, */
/* 		  NULL); */

    str = XmStringCreateLocalized((char *)label);
    n = 0;
    XtSetArg(args[n], XmNsubMenuId, menu); n++;
    XtSetArg(args[n], XmNlabelString, str); n++;
    XtSetArg(args[n], XmNuserData, NULL); n++;
    cascade = XmCreateOptionMenu(parent, (char *)name, args, n);
    if (top)
	XtVaSetValues(cascade,
		      XmNtopAttachment, XmATTACH_WIDGET,
		      XmNtopWidget, top,
		      XmNleftAttachment, XmATTACH_FORM,
		      NULL);
    else
	XtVaSetValues(cascade,
		      XmNtopAttachment, XmATTACH_FORM,
		      XmNleftAttachment, XmATTACH_FORM,
		      NULL);
    XmStringFree(str);

    if (ptr != NULL) {
	item = XtVaCreateManagedWidget(ptr, xmPushButtonGadgetClass, menu,
				       XmNuserData, tinfo,
				       NULL);
	XtAddCallback(item, XmNactivateCallback, select_cb, menu); 
    }
    
    for (i = 0; command_list[i] != NULL; i++) {
	item = XtVaCreateManagedWidget(command_list[i], xmPushButtonGadgetClass, menu,
				    XmNuserData, tinfo,
				    NULL);
	XtAddCallback(item, XmNactivateCallback, select_cb, menu); 
    }
    XtManageChild(cascade);
    return cascade;
#endif
}

Widget
prefs_helpers(struct topic_info *tinfo)
{
/*     struct prefs_choice *prefs = (struct prefs_choice *)info->data; */
    Widget form, /* frame, */ form1;
    Widget browser_command, editor_command;

    form = XmCreateForm(tinfo->right_form, "helpers_form", NULL, 0);

/*     frame = XmCreateFrame(form, "commands_frame", NULL, 0); */

    form1 = XtVaCreateWidget("commands_form", xmFormWidgetClass,
			     form,
			     XmNverticalSpacing, 10,
			     XmNhorizontalSpacing, 0,
			     NULL);

    if (m_browser_list == NULL)
	m_browser_list = get_separated_list(resource.prefs_browser_list, "\n", False);
    
    if (m_editor_list == NULL)
	m_editor_list = get_separated_list(resource.prefs_editor_list, "\n", False);
    
    browser_command = h_create_command(Xdvi_BROWSER_COMBO_NAME,
				       Xdvi_BROWSER_MENU_NAME,
				       "Web Browser: ",
				       resource.browser,
				       form1, NULL,
				       m_browser_list,
				       select_browser_cb, tinfo);
    
    editor_command = h_create_command(Xdvi_EDITOR_COMBO_NAME,
				      Xdvi_EDITOR_MENU_NAME,
				      "Editor for Source Specials: ",
				      resource.editor,
				      form1, browser_command,
				      m_editor_list,
				      select_editor_cb, tinfo);
    
/* #if PS_GS */
/*     Widget b2 = XmCreateLabelGadget(rowcol, "[x] Use Ghostscript to interpret PS specials", NULL, 0); */

    /* TODO: don't need this??? Similar: ps2pdf, dvips conversion?? */
    /*     Widget b3 = XmCreateLabelGadget(rowcol, "Path to Ghostscript: ____________", NULL, 0); */
    
/* #endif */
/*     Widget b4 = XmCreateLabelGadget(rowcol, "Editor for source specials: <pulldown> [Other ...]", NULL, 0); */
/*     XtManageChild(b1); */
/*     XtManageChild(b2); */
/*     XtManageChild(b3); */
/*     XtManageChild(b4); */
/*     XtManageChild(rowcol); */

    XtManageChild(form1);
/*     XtManageChild(frame); */

    return form;
}

#else
/* silence `empty compilation unit' warnings */
static void bar(void); static void foo() { bar(); } static void bar(void) { foo(); }
#endif /* MOTIF */

