/*-
 * Copyright (c) 1996
 *	Rob Zimmermann.  All rights reserved.
 * Copyright (c) 1996
 *	Keith Bostic.  All rights reserved.
 *
 * See the LICENSE file for redistribution information.
 */

#include "config.h"

#ifndef lint
static const char sccsid[] = "$Id: m_options.c,v 8.22 2003/11/05 17:09:59 skimo Exp $ (Berkeley) $Date: 2003/11/05 17:09:59 $";
#endif /* not lint */

#include <sys/types.h>
#include <sys/queue.h>

#include <X11/X.h>
#include <X11/Intrinsic.h>
#include <Xm/DialogS.h>
#include <Xm/Form.h>
#include <Xm/Frame.h>
#include <Xm/LabelG.h>
#include <Xm/PushBG.h>
#include <Xm/TextF.h>
#include <Xm/ToggleBG.h>
#include <Xm/RowColumn.h>

#include <bitstring.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#undef LOCK_SUCCESS
#include "../common/common.h"
#include "../ipc/ip.h"
#include "m_motif.h"

extern int vi_ofd;

static void set_opt __P((Widget, XtPointer, XtPointer));


/* constants */

#if defined(SelfTest)

/* in production, get these from the resource list */

#define	toggleColumns	6

#endif


/*
 * global data
 */

static Widget	preferences = NULL;

static optData display[] = {
	{ optToggle,	"comment",	},
	{ optToggle,	"flash",	},
	{ optToggle,	"leftright",	},
	{ optToggle,	"list",		},
	{ optToggle,	"number",	},
	{ optToggle,	"octal",	},
	{ optToggle,	"ruler",	},
	{ optToggle,	"showmode",	},
	{ optToggle,	"slowopen",	},
	{ optToggle,	"verbose",	},
	{ optToggle,	"windowname",	},
	{ optTerminator,		},
}, display_int[] = {
	{ optInteger,	"report",	},
	{ optInteger,	"scroll",	},
	{ optInteger,	"shiftwidth",	},
	{ optInteger,	"sidescroll",	},
	{ optInteger,	"tabstop",	},
	{ optInteger,	"window",	},
	{ optTerminator,		},
}, display_str[] = {
	{ optString,	"noprint",	},
	{ optString,	"print",	},
	{ optTerminator,		},
}, files[] = {
	{ optToggle,	"autowrite",	},
	{ optToggle,	"lock",		},
	{ optToggle,	"readonly",	},
	{ optToggle,	"writeany",	},
	{ optTerminator,		},
}, files_str[] = {
	{ optString,	"backup",	},
	{ optString,	"path",		},
	{ optTerminator,		},
}, general[] = {
	{ optToggle,	"exrc",		},
	{ optToggle,	"lisp",		},
	{ optToggle,	"modeline",	},
	{ optToggle,	"sourceany",	},
	{ optToggle,	"tildeop",	},
	{ optTerminator,		},
}, general_int[] = {
	{ optInteger,	"taglength",	},
	{ optTerminator,		},
}, general_str[] = {
	{ optString,	"cdpath",	},
	{ optString,	"directory",	},
	{ optString,	"msgcat",	},
	{ optString,	"recdir",	},
	{ optString,	"shell",	},
	{ optString,	"shellmeta",	},
	{ optString,	"tags",		},
	{ optTerminator,		},
}, input[] = {
	{ optToggle,	"altwerase",	},
	{ optToggle,	"autoindent",	},
	{ optToggle,	"remap",	},
	{ optToggle,	"showmatch",	},
	{ optToggle,	"ttywerase",	},
	{ optTerminator,		},
}, input_int[] = {
	{ optInteger,	"escapetime",	},
	{ optInteger,	"keytime",	},
	{ optInteger,	"matchtime",	},
	{ optInteger,	"timeout",	},
	{ optInteger,	"wraplen",	},
	{ optInteger,	"wrapmargin",	},
	{ optTerminator,		},
}, input_str[] = {
	{ optString,	"cedit",	},
	{ optString,	"filec",	},
	{ optTerminator,		},
}, search[] = {
	{ optToggle,	"extended",	},
	{ optToggle,	"iclower",	},
	{ optToggle,	"ignorecase",	},
	{ optToggle,	"magic",	},
	{ optToggle,	"searchincr",	},
	{ optToggle,	"wrapscan",	},
	{ optTerminator,		},
}, search_str[] = {
	{ optString,	"paragraphs",	},
	{ optString,	"sections",	},
	{ optTerminator,		},
};

/* ********* NOTE ***********
 * Sheet 0 will always be shown first.  It does not matter to the Xt code
 * which sheet that is, so it ought to be the one users interact with most.
 * Best guess is that's general editor options, but it might be Search/re.
 * ********* NOTE ***********
 */
static	optSheet sheets[] = {
	{	"Display",
		"These options control how text is displayed on the screen",
		NULL,
		display,
		display_int,
		display_str,
	},
	{	"Files",
		"These options control how the editor handles files",
		NULL,
		files,
		NULL,
		files_str,
	},
	{	"Input",
		"These options control text input behavior",
		NULL,
		input,
		input_int,
		input_str,
	},
	{	"Search/RE",
	"These options control searching and Regular Expression behavior",
		NULL,
		search,
		NULL,
		search_str,
	},
	{	"Editor",
		"These options control general editor configuration",
		NULL,
		general,
		general_int,
		general_str,
	},
};


/* callbacks */

#if defined(SelfTest)
void __vi_cancel_cb()
{
    puts( "cancelled" );
}
#endif


static	void destroyed(void)
{
    int i;

    puts( "destroyed" );

    /* some window managers destroy us upon popdown */
    for (i=0; i<XtNumber(sheets); i++) {
	sheets[i].holder = NULL;
    }
    preferences = NULL;
}


static	void	window_unmapped(Widget w, XtPointer ptr, XEvent *ev, Boolean *cont)
{
    if ( ev->type == UnmapNotify ) {
#if defined(SelfTest)
	puts( "unmapped" );
#endif
	XtPopdown( XtParent( preferences ) );
    }
}

/*
 * __vi_editopt --
 *	Set an edit option based on a core message.
 *
 * PUBLIC: int __vi_editopt __P((IPVI *, const char *, u_int32_t, const char *, u_int32_t, u_int32_t));
 */
int
__vi_editopt(IPVI *ipvi, const char *str1, u_int32_t len1, const char *str2, u_int32_t len2, u_int32_t val1)
{
	optData *opt;

#undef	NSEARCH
#define	NSEARCH(list) {							\
	for (opt = list; opt->kind != optTerminator; ++opt)		\
		if (!strcmp(opt->name, str1))			\
			goto found;					\
}

	NSEARCH(display);
	NSEARCH(display_int);
	NSEARCH(display_str);
	NSEARCH(files);
	NSEARCH(files_str);
	NSEARCH(general);
	NSEARCH(general_int);
	NSEARCH(general_str);
	NSEARCH(input);
	NSEARCH(input_int);
	NSEARCH(input_str);
	NSEARCH(search);
	NSEARCH(search_str);

	return (0);

found:	switch (opt->kind) {
	case optToggle:
		opt->value = (void *)val1;
		break;
	case optInteger:
		if (opt->value != NULL)
			free(opt->value);
		if ((opt->value = malloc(8)) != NULL)
			(void)snprintf(opt->value,
			    8, "%lu", (u_long)val1);
		break;
	case optString:
	case optFile:
		if (opt->value != NULL)
			free(opt->value);
		if ((opt->value = malloc(len2)) != NULL)
			memcpy(opt->value, str2, len2);
		break;
	case optTerminator:
		abort();
	}
	return (0);
}

/*
 * set_opt --
 *	Send a set-edit-option message to core.
 */
static void
set_opt(Widget w, XtPointer closure, XtPointer call_data)
{
	optData *opt;
	Boolean set;
	IP_BUF ipb;
	String str;
	extern IPVI ipvi_motif;

	opt = closure;

	ipb.code = VI_EDITOPT;
	ipb.str1 = opt->name;
	ipb.len1 = strlen(opt->name);

	switch (opt->kind) {
	case optToggle:
		XtVaGetValues(w, XmNset, &set, 0);
		ipb.val1 = set;
		ipb.len2 = 0;

		vi_wsend(&ipvi_motif, "ab1", &ipb);
		if (ipb.val1) {
			opt->value = (void *)!set;
			/*
			 * RAZ:
			 * How do we turn off the button?  We don't want to
			 * go recursive where we set it and it calls set_opt
			 * to tell the core.  Is that possible?
			 */
			XtVaSetValues(w, XmNset, &set, 0);
			break;
		}

		if (strcmp(opt->name, "ruler") == 0)
			if (set)
				__vi_show_text_ruler_dialog(
				    __vi_screen->area, "Ruler");
			else
				__vi_clear_text_ruler_dialog();
		break;
	case optInteger:
		str = XmTextFieldGetString(w);
		ipb.val1 = atoi(str);
		ipb.len2 = 0;
		vi_send(vi_ofd, "ab1", &ipb);
		break;
	case optFile:
	case optString:
		ipb.str2 = XmTextFieldGetString(w);
		ipb.len2 = strlen(ipb.str2);
		vi_send(vi_ofd, "ab1", &ipb);
		break;
	case optTerminator:
		abort();
	}
}


/* add toggles to the property sheet */

#if defined(__STDC__)
static	void	add_toggle( Widget parent, optData *option )
#else
static	void	add_toggle( parent, option )
	Widget	parent;
	optData	*option;
#endif
{
    Widget	w;

    w = XtVaCreateManagedWidget( option->name,
				 xmToggleButtonGadgetClass,
				 parent,
				 XmNset,	(Boolean) option->value,
				 0
				 );
    XtAddCallback( w, XmNvalueChangedCallback, set_opt, option );
}


static	Widget	create_toggles(Widget outer, optData *toggles)
{
    Widget	inner;
    int		i;

    inner = XtVaCreateWidget( "toggleOptions",
			      xmRowColumnWidgetClass,
			      outer,
			      XmNpacking,		XmPACK_COLUMN,
			      XmNnumColumns,		4,
			      XmNtopAttachment,		XmATTACH_FORM,
			      XmNrightAttachment,	XmATTACH_FORM,
			      XmNleftAttachment,	XmATTACH_FORM,
			      0
			      );

    /* first the booleans */
    for (i=0; toggles[i].kind != optTerminator; i++) {
	add_toggle( inner, &toggles[i] );
    }
    XtManageChild( inner );

    return inner;
}


/* draw text fields and their labels */

#if defined(__STDC__)
static	void	add_string_options( Widget parent,
				    optData *options
				    )
#else
static	void	add_string_options( parent, options )
	Widget	parent;
	optData	*options;
#endif
{
    int		i;
    Widget	f, w;

    for ( i=0; options[i].kind != optTerminator; i++ ) {

	f = XtVaCreateWidget( "form",
			      xmFormWidgetClass,
			      parent,
			      0
			      );

	XtVaCreateManagedWidget( options[i].name,
				 xmLabelGadgetClass,
				 f,
				 XmNtopAttachment,	XmATTACH_FORM,
				 XmNbottomAttachment,	XmATTACH_FORM,
				 XmNleftAttachment,	XmATTACH_FORM,
				 XmNrightAttachment,	XmATTACH_POSITION,
				 XmNrightPosition,	20,
				 XmNalignment,		XmALIGNMENT_END,
				 0
				 );

	w = XtVaCreateManagedWidget( "text",
				     xmTextFieldWidgetClass,
				     f,
				     XmNtopAttachment,		XmATTACH_FORM,
				     XmNbottomAttachment,	XmATTACH_FORM,
				     XmNrightAttachment,	XmATTACH_FORM,
				     XmNleftAttachment,		XmATTACH_POSITION,
				     XmNleftPosition,		20,
				     0
				     );

	XmTextFieldSetString( w, (char *) options[i].value );
	XtAddCallback( w, XmNactivateCallback, set_opt, &options[i] );
	XtManageChild( f );
    }
}


/* draw and display a single page of properties */

#if defined(__STDC__)
static	Widget		create_sheet( Widget parent, optSheet *sheet )
#else
static	Widget		create_sheet( parent, sheet )
	Widget		parent;
	optSheet	*sheet;
#endif
{
    Widget	outer, inner, frame;
    Dimension	height;
    XmString	str;

    outer = XtVaCreateWidget( sheet->name,
			      xmFormWidgetClass,
			      parent,
			      XmNtopAttachment,		XmATTACH_FORM,
			      XmNrightAttachment,	XmATTACH_FORM,
			      XmNbottomAttachment,	XmATTACH_FORM,
			      XmNleftAttachment,	XmATTACH_FORM,
			      XmNshadowType,		XmSHADOW_ETCHED_IN,
			      XmNshadowThickness,	2,
			      XmNverticalSpacing,	4,
			      XmNhorizontalSpacing,	4,
			      0
			      );

    /* add descriptive text */
    frame = XtVaCreateManagedWidget( "frame",
				    xmFrameWidgetClass,
				    outer,
				    XmNtopAttachment,		XmATTACH_FORM,
				    XmNrightAttachment,		XmATTACH_FORM,
				    XmNleftAttachment,		XmATTACH_FORM,
				    0
				    );
    str = XmStringCreateLtoR( sheet->description, XmSTRING_DEFAULT_CHARSET );
    XtVaCreateManagedWidget( "description",
			     xmLabelGadgetClass,
			     frame,
			     XmNlabelString,		str,
			     0
			     );
    XmStringFree( str );

    /* Add the toggles. */
    inner = create_toggles( outer, sheet->toggles );
    XtVaSetValues( inner,
		   XmNtopAttachment,	XmATTACH_WIDGET,
		   XmNtopWidget,	frame,
		   0
		   );

    /* the string options go here */
    inner = XtVaCreateWidget( "otherOptions",
			      xmRowColumnWidgetClass,
			      outer,
			      XmNpacking,		XmPACK_COLUMN,
			      XmNtopAttachment,		XmATTACH_WIDGET,
			      XmNtopWidget,		inner,
			      XmNrightAttachment,	XmATTACH_FORM,
			      XmNbottomAttachment,	XmATTACH_FORM,
			      XmNleftAttachment,	XmATTACH_FORM,
			      0
			      );

    /* Optionally, the ints. */
    if ( sheet->ints != NULL )
	add_string_options( inner, sheet->ints );

     /* Optionally, the rest. */
    if ( sheet->others != NULL )
	add_string_options( inner, sheet->others );

    XtManageChild( inner );

    /* finally, force resize of the parent */
    XtVaGetValues( outer, XmNheight, &height, 0 );
    XtVaSetValues( parent, XmNheight, height, 0 );

    return outer;
}


/* change preferences to another sheet */

static	void	change_sheet(Widget parent, int current)
{
    static int		current_sheet = -1;

    /* create a new one? */
    if ( sheets[current].holder == NULL )
	sheets[current].holder = create_sheet( parent, &sheets[current] );

    /* done with the old one? */
    if ( current_sheet != -1 && sheets[current_sheet].holder != NULL )
	XtUnmanageChild( sheets[current_sheet].holder );

    current_sheet = current;
    XtManageChild( sheets[current].holder );
    XtManageChild( parent );
}


/* Draw and display a dialog the describes vi options */

#if defined(__STDC__)
static	Widget	create_options_dialog( Widget parent, String title )
#else
static	Widget	create_options_dialog( parent, title )
	Widget	parent;
	String	title;
#endif
{
    Widget	box, form, inner;
    int		i;
    char	buffer[1024];

    /* already built? */
    if ( preferences != NULL ) return preferences;

    box = XtVaCreatePopupShell( title,
				xmDialogShellWidgetClass,
				parent,
				XmNtitle,		title,
				XmNallowShellResize,	False,
				0
				);
    XtAddCallback( box, XmNpopdownCallback, __vi_cancel_cb, 0 );
    XtAddCallback( box, XmNdestroyCallback, destroyed, 0 );
    XtAddEventHandler( box,
		       SubstructureNotifyMask,
		       False,
		       window_unmapped,
		       NULL
		       );

    form = XtVaCreateWidget( "options", 
			     xmFormWidgetClass,
			     box,
			     0
			     );

    /* copy the pointers to the sheet names */
    *buffer = '\0';
    for (i=0; i<XtNumber(sheets); i++) {
	strcat( buffer, "|" );
	strcat( buffer, sheets[i].name );
    }

    inner = __vi_CreateTabbedFolder( "tabs",
				    form,
				    buffer,
				    XtNumber(sheets),
				    change_sheet
				    );

    /* build the property sheets early */
    for ( i=0; i<XtNumber(sheets); i++ )
	change_sheet( inner, i );

    /* manage all of the sheets right now */
    for ( i=0; i<XtNumber(sheets); i++ )
	XtManageChild( sheets[i].holder );
    XtManageChild( form );

    /* remove all but the first one */
    for ( i=0; i<XtNumber(sheets); i++ )
	XtUnmanageChild( sheets[i].holder );
    change_sheet( inner, 0 );	/* show first sheet first */

    /* keep this global, we might destroy it later */
    preferences = form;

    /* done */
    return form;
}



/*
 * module entry point
 *
 * __vi_show_options_dialog --
 *
 *
 * PUBLIC: void __vi_show_options_dialog __P((Widget, String));
 */
void
__vi_show_options_dialog(Widget parent, String title)
{
    Widget 	db = create_options_dialog( parent, title );
#if defined(SelfTest)
    Widget	shell = XtParent( db );
#endif

    XtManageChild( db );

#if defined(SelfTest)
    /* wait until it goes away */
    XtPopup( shell, XtGrabNone );
#else
    /* wait until it goes away */
    __vi_modal_dialog( db );
#endif
}



/* module entry point
 * Utilities for the search dialog
 *
 * __vi_toggle --
 *	Returns the current value of a toggle.
 *
 * PUBLIC: int __vi_toggle __P((char *));
 */
int
__vi_toggle(char *name)
{
	optData *opt;

#undef	NSEARCH
#define	NSEARCH(list) {							\
	for (opt = list; opt->kind != optTerminator; ++opt)		\
		if (!strcmp(opt->name, name))				\
			return ((int)opt->value);			\
}
	NSEARCH(display);
	NSEARCH(files);
	NSEARCH(general);
	NSEARCH(input);
	NSEARCH(search);

	return (0);
}

/*
 * __vi_create_search_toggles --
 *	Creates the search toggles.  This is so the options and search widgets
 *	share their appearance.
 *
 * PUBLIC: Widget __vi_create_search_toggles __P((Widget, optData[]));
 */
Widget
__vi_create_search_toggles(Widget parent, optData *list)
{
	optData *opt;

	/*
	 * Copy current options information into the search table.
	 *
	 * XXX
	 * This is an O(M*N) loop, but I don't think it matters.
	 */
	for (opt = list; opt->kind != optTerminator; ++opt)
		opt->value = (void *)__vi_toggle(opt->name);

	return (create_toggles(parent, list));
}


#if defined(SelfTest)

#if defined(__STDC__)
static void show_options( Widget w, XtPointer data, XtPointer cbs )
#else
static void show_options( w, data, cbs )
Widget w;
XtPointer	data;
XtPointer	cbs;
#endif
{
    __vi_show_options_dialog( data, "Preferences" );
}

main( int argc, char *argv[] )
{
    XtAppContext	ctx;
    Widget		top_level, rc, button;
    extern		exit();

    /* create a top-level shell for the window manager */
    top_level = XtVaAppInitialize( &ctx,
				   argv[0],
				   NULL, 0,	/* options */
				   (ArgcType) &argc,
				   argv,	/* might get modified */
				   NULL,
				   NULL
				   );

    rc = XtVaCreateManagedWidget( "rc",
				  xmRowColumnWidgetClass,
				  top_level,
				  0
				  );

    button = XtVaCreateManagedWidget( "Pop up options dialog",
				      xmPushButtonGadgetClass,
				      rc,
				      0
				      );
    XtAddCallback( button, XmNactivateCallback, show_options, rc );

    button = XtVaCreateManagedWidget( "Quit",
				      xmPushButtonGadgetClass,
				      rc,
				      0
				      );
    XtAddCallback( button, XmNactivateCallback, exit, 0 );

    XtRealizeWidget(top_level);
    XtAppMainLoop(ctx);
}
#endif
