/*
**		BRIEF -- Basic Reconfigurable Interactive Editing Facility
**
**
**		dialog.cb:
**
**		This file contains all of the standard BRIEF macros for the
**	dialog and menu manager.
*/

#define	DIALOG_MAIN
#include "dialog.h"
#include "dlg.h"
#include "win_ctrl.h"

/*
**		Dialog Manager Package:  Note to Macro Programmers
**
**		Input to an interactive macro must ultimately come from the
**	keyboard.  In addition to the primitive calls for keyboard
**	handling (assign_to_key, get_parm, read_char, etc.) we have
**	provided two high-level calls which provide a simple, consistent,
**	and attractive user interface.  We strongly recommend that you
**	use the built-in dialog manager calls in your macros instead of
**	writing your own user interface.
*/


/*
**		Global variables.
*/

int		_dialog_level,
			_dialog_type,
			_dialog_row,
			_dialog_col,
			_dialog_field_keymap,
			_dialog_list_keymap,
			_dialog_menu_keymap,
			_dialog_button_keymap,
			_dialog_size,
			_dialog_data_buf,
			_dialog_disp_buf,
			_dialog_radio_buf,
			_dialog_mouse_buf,
			_dialog_mode,
			_dialog_menu_time,
			_dialog_picked,
	 		_dialog_pb_state;

string	_dialog_action_func,
		_dialog_dir,
		_dialog_menu_prefix,
		_dialog_default_pb;


/*
**		init:
**
**		Initialize the dialog manager package.  This must always be
**	the first call to the dialog manager; it is run whenever the
**	file is loaded.
*/

void _init ()
{
	if (first_time ())
		{

		/*
		**		First we get the name of the directory in which the
		**	menu files and help files are kept.
		*/

		if (!strlen (_dialog_dir = inq_environment ("BHELP")))
			_dialog_dir = "/brief/help";

		/*
		**		We now create 3 keymaps; the only assignments they have
		**	in common are the grey - and the Esc key.
		**
		**		Here we create a keymap for use with menus.	Note that
		**	at every level you can use the INIT and TERM hooks to
		**	add your own key assignments to this keymap; just
		**	remember to restore the old assignments, or your keys
		**	will be wrong when you return to a lower-level menu.
		*/

		keyboard_push ();
		assign_to_key ("<Down>", "_dialog_menu_down");
		assign_to_key ("<Space>", "_dialog_menu_down");
		assign_to_key ("<Up>", "_dialog_menu_up");
		assign_to_key ("<Backspace>", "_dialog_menu_up");
		assign_to_key ("<Enter>", "_dialog_menu_pick");
		assign_to_key ("<Keypad-Enter>", "_dialog_menu_pick");
		assign_to_key ("<Esc>", "_dialog_esc");
		assign_to_key ("<Keypad minus>", "_dialog_grey_minus");
		assign_to_key ("<Home>", "_dialog_menu_home");
		assign_to_key ("<Ctrl-Home>", "_dialog_menu_home");
		assign_to_key ("<End>", "_dialog_menu_end");
		assign_to_key ("<Ctrl-End>", "_dialog_menu_end");
		assign_to_key ("<PgUp>", "_dialog_menu_pgup");
		assign_to_key ("<PgDn>", "_dialog_menu_pgdn");
		_dialog_menu_keymap = inq_keyboard ();
		keyboard_pop (TRUE);

		/*
		**	Here we create a keymap for use with lists.	It also
		**	contains the general field-to-field key assignments.
		*/

		keyboard_push ();
		assign_to_key ("<F10>", "_dialog_f10");
		assign_to_key ("<Enter>", "_dialog_next_and_save");
		assign_to_key ("<Keypad-Enter>", "_dialog_next_and_save");
		assign_to_key ("<Esc>", "_dialog_esc");
		assign_to_key ("<Keypad minus>", "_dialog_grey_minus");
		assign_to_key ("<Tab>", "_dialog_next");
		assign_to_key ("<Down>", "_dialog_next");
		assign_to_key ("<Shift-Tab>", "_dialog_prev");
		assign_to_key ("<Up>", "_dialog_prev");
		assign_to_key ("<Ctrl-Home>", "_dialog_home");
		assign_to_key ("<Ctrl-End>", "_dialog_end");
		assign_to_key ("<Backspace>", "_list_prev");
		assign_to_key ("<Left>", "_list_prev");
		assign_to_key ("<Space>", "_list_next");
		assign_to_key ("<Right>", "_list_next");
		assign_to_key ("<Home>", "_list_home");
		assign_to_key ("<End>", "_list_end");
		_dialog_list_keymap = inq_keyboard ();
		keyboard_pop (TRUE);


		/*
		**		Create a keymap for use with buttons.
		*/

		keyboard_push ();
		assign_to_key ("<Space>", "_dlg_pick_btn");
		assign_to_key ("<Backspace>", "_dlg_beep");
		assign_to_key ("<Home>", "_dlg_beep");
		assign_to_key ("<End>", "_dlg_beep");
		assign_to_key ("<Left>", "_dlg_btn_prev");
		assign_to_key ("<Up>", "_dlg_btn_prev");
		assign_to_key ("<Right>", "_dlg_btn_next");
		assign_to_key ("<Down>", "_dlg_btn_next");
		assign_to_key ("<Tab>", "_dlg_btn_tab_next");
		assign_to_key ("<Shift-Tab>", "_dlg_btn_tab_prev");
		_dialog_button_keymap = inq_keyboard ();
		keyboard_pop (TRUE);

		/*
		**	Here we create a keymap for use with typed fields.
		**	When we're done, we restore the previous keymap.
		*/

		keyboard_push ();
		assign_to_key ("<Backspace>", "_field_bksp");
		assign_to_key ("<Space>", "_field_insert 32");
		assign_to_key ("<Left>", "_field_left");
		assign_to_key ("<Right>", "_field_right");
		assign_to_key ("<Del>", "_field_del");
		assign_to_key ("<Home>", "_field_home");
		assign_to_key ("<End>", "_field_end");
		assign_to_key ("<Alt-k>", "_field_delete_to_eol");
		assign_to_key ("<Alt-d>", "_field_del_line");
		assign_to_key ("<Ins>", "insert_mode");
		assign_to_key ("<Alt-i>", "insert_mode");
		_dialog_field_keymap = inq_keyboard ();
		keyboard_pop (TRUE);

		/*
		**		Autoload the list, field, menu and button functions.
		*/

		autoload ("dlg_list", "_list_next", "_list_prev", "_list_highlight",
						"_list_home", "_list_end", "_list_go", "_list_button",
						"_is_list");

		autoload ("dlg_fld", "_field_insert", "_field_bksp", "_field_left",
						"_field_right", "_field_del", "_field_end", "_field_home",
						"_field_del_line", "_field_delete_to_eol",
						"_dialog_validate", "is_filename", "at_eol",
						"_field_contents");

		autoload ("dlg_menu", "_menu_highlight", "_dialog_menu_home",
						"_dialog_menu_end", "_dialog_menu_pgup",
						"_dialog_menu_pgdn", "_dialog_menu_down",
						"_dialog_menu_up", "_menu_go", "_dialog_menu_pick",
						"_menu_button", "mouse_menu");

		autoload ("dlg_butn", "_dialog_check_button", "_dialog_is_checked",
						"_dialog_check_radio", "_dialog_radio_checked",
						"_dlg_goto_btn", "_dlg_check_btn", "_dlg_push_btn",
						"_dlg_radio_btn", "_dlg_radio_grp", "_dlg_pick_btn",
						"_dlg_select_push", "_dlg_btn_tab_next",
						"_dlg_btn_tab_prev", "_dlg_btn_next", "_dlg_btn_prev",
						"_is_push_button");
		
		autoload ( "dlg_mous", "mouse_dialog" ); 

		}
}

/*
**		_remove_dialog_manager:
**
**		You should call this macro whenever you are finished using the
**	dialog manager.  It frees the keymaps created when we load.
**	It would be nice to have this function unload the macro file, but
**	that's for a future release.
**
**		Don't call this macro if you plan to use the dialog manager
**	again in the same session.
*/

void _remove_dialog_manager (void)
{
	if (first_time ())
		{
		keyboard_push (_dialog_menu_keymap);
		keyboard_push (_dialog_list_keymap);
		keyboard_push (_dialog_field_keymap);
		keyboard_push (_dialog_button_keymap);
		keyboard_pop ();
		keyboard_pop ();
		keyboard_pop ();
		keyboard_pop ();
		}
}

/*
**		_dialog_f10, _dialog_esc, _dialog_grey_minus:
**
**		Call the action function with an appropriate code and exit
**	from the dialog box, menu, or entire dialog manager.
*/

void _dialog_f10 (void)
{
	/*
	**		When the F10 key is pressed, we have to verify the
	**	current field before we can exit.  This function is
	**	only used in DIALOG_DBOX_MODE.
	*/

	if (_dialog_exit () && execute_macro (_dialog_action_func, DIALOG_F10))
		_exit ();
}

void _dialog_esc (void)
{
	int	curr_level;

	execute_macro (_dialog_action_func, DIALOG_ESCAPE);
	curr_level = _dialog_level;

	while (curr_level--)
		_exit ();
}

void _dialog_grey_minus (void)
{
	/*
	**		When the grey - key is pressed, we immediately exit one level.
	*/

	execute_macro (_dialog_action_func, DIALOG_GREY_MINUS);
	_exit ();
}

/*
**		_format_dialog:
**
**		Parses the information buffer and formats the display buffer,
**	leaving the display buffer current.
*/

void _format_dialog ()
{
	int	pos,
			open_paren,
			default_btn,
			in_group,
			first_in_group;

	string	line,
				group;

	_dialog_default_pb = "";
	default_btn = 1;
	_dialog_pb_state = TRUE; 	// no need to draw pb 

	set_buffer (_dialog_disp_buf);
	tabs (2);
	set_buffer (_dialog_data_buf);
	inq_names (NULL, NULL, line);
	execute_macro (_dialog_action_func, DIALOG_CREATE_DBOX, NULL, line);
	top_of_buffer ();

	/*
	**		Display all fields.	We allow white space before the
	**	format commands in the buffer.
	*/

	while (search_fwd (in_group ? FIND_RADIO : FIND_ANY, TRUE, FALSE))
		{
		pos = index (line = _dialog_set_globals (), "\"") + 1;
		line = substr (line, pos, rindex (line, "\"") - pos);
		next_char ();

		/*
		**		Move this up here when I can explain why...
		*/

		open_paren = TRUE;

		switch (_dialog_type)
			{
			case LIST:
				{
				/*
				**		If we are formatting a list, we first make sure there's
				**	a current item.  If there isn't, we must skip over the
				**	field since it could create serious errors later.	If
				**	there is a current item but it's not the first item,
				**	we prepend a tab to the list so we'll be able to find
				**	the first button.  We also stick a tab at the end of the
				**	list so we can find the end of the last item.
				*/

				if (_dialog_type == LIST)
					{
					if (open_paren = index (line, "("))
						{
						if (open_paren > 1)
							line = "\t" + line;

						_dialog_col--;
						line += "\t";
						}
					}
				}

			case GROUP:
				{

				/*
				**		Disallow 'nested' groups.
				*/

				if (in_group)
					{
					delete_line ();
					beginning_of_line ();
					error ("Extra 'GROUP' label ignored.");
					}
				else
					{
					group = line;
					in_group++;
					first_in_group = TRUE;
					}

				continue;
				}

			case ENDGROUP:
				{

				/*
				**		Can't have one of these w/o a preceeding 'Group' label.
				*/

				if (!in_group)
					{
					delete_line ();
					beginning_of_line ();
					error ("Extra 'ENDGROUP' ignored.");
					}
				else
					{
					//set_buffer (_dialog_radio_buf);
					//insert ("[EndGroup:%s]\n", group);
					group = "";
					in_group--;
					}

				continue;
				}

			case RADIO:
				{
				int radio_char;

				/*
				**		If we are not in a 'Group' section, toss this line.
				*/

				if (!in_group)
					{
					delete_line ();
					beginning_of_line ();
					error ("Missing 'GROUP' label -- Radio button ignored.");
					continue;
					}

				/*
				**		For each radio button, store the button's group, label,
				**	position, and status in _dialog_radio_buf.  By default, the
				** first radio button of each group is set to 'on' and all others
				** in the same group are set to 'off'
				*/

				set_buffer (_dialog_radio_buf);

				if (first_in_group)
					{
					radio_char = RADIO_ON;
					first_in_group = FALSE;
					}
				else
					radio_char = RADIO_OFF;

				insert ("%s\"%s\"(%d,%d)%c\n", group, line, _dialog_row,
							_dialog_col, radio_char);

				set_buffer (_dialog_data_buf);

				sprintf (line, RADIO_BTN, radio_char, line);
				}

			case PUSH:
				{
				/*
				**		If this pushbutton is the default, set _dialog_default_pb.
				*/

				if (search_string ("<[ \t]@P*([ \t0-9]+,[ \t0-9])[ \t]@DEFAULT", group))
					_dialog_default_pb = line;

				sprintf (line, PUSH_BUTTON, line);
				}

			case CHECK:
				{
				/*
				**		Build Checkbox button and label for insertion;
				*/

				line = CHECK_BOX + " " + line;
				}
			}

		/*
		**		Likewise, if we encounter a field in the data buffer
		**	that is too long, we delete it and skip over it
		**	in the formatting process.
		*/

		pos = (_dialog_col + strlen (line)) - _dialog_size;

		if (!open_paren || pos > 0)
			{
			if (open_paren)
				error ("Field %d columns too wide for window.", pos);
			else
				error ("No current item in list.");

			delete_line ();
			beginning_of_line ();
			}
		else
			{
			set_buffer (_dialog_disp_buf);
			move_abs (_dialog_row, _dialog_col);

			/*
			**		This makes sure we're not already past end of line
			**	if a field is blank to begin with.
			*/

			if (strlen (line))
				insert (line);
			else
				{
				insert ("\t");
				prev_char ();
				delete_char ();
				}

			/*
			**		If this is not a static-text control, assign a mouse event to
			**	the control.
			*/

			if (_dialog_type != TEXT)
				{
				int		width,
							ctrl_width;

				/*		If the control is not a button, set the ctrl_width to the
				**	maximum width of the field.
				*/

				if (_dialog_type != CHECK && _dialog_type != PUSH && 
					 _dialog_type != RADIO && _dialog_type != LIST)
					ctrl_width = _dialog_size - _dialog_col - 1;
				else
					ctrl_width = strlen (line);

				width = ctrl_width;

				/* set to the mouse buffer */

				set_buffer(_dialog_mouse_buf);

				/* Insert the type of control in the buffer.	*/

				move_abs(_dialog_row, _dialog_col + (_dialog_type == LIST));

				if (_dialog_type == LIST)
					ctrl_width -= 3;

				for ( ;ctrl_width > 0; ctrl_width --)
					insert("%d", _dialog_type);
	
				/* get rid of extra chars inserted */

				delete_char(width);	
				}

			set_buffer (_dialog_data_buf);
			}

		}
	top_of_buffer ();
	search_fwd (FIND_CONTROL, TRUE, FALSE);
	_dialog_enter ();
}

/*
**		_process_dialog_box:
**
**		Creates a dialog box window on the screen and a display
**	buffer to go in it.	Makes sure an information buffer exists.
**	Calls _format_dialog to prepare the display buffer.  Then makes
**	the first field in the dialog box current and highlights it.
**	Enters interactive mode, calling the user's action function
**	whenever "interesting" events occur.
**
**	When the user exits, this function deletes the display buffer
**	and dialog box window, deletes the data buffer as well if that
**	buffer is a system buffer, pops the keymaps that were created,
**	restores the editing mode and tab/space mode, etc.
*/

int _process_dialog_box (int lx, int by, int rx, int ty, string, string, ~string, ~int, string)
{
	int	old_buf_id,
			created_buffer,
			old_type,
			old_row,
			old_col,
			old_size,
			old_data_buf,
			old_disp_buf,
			old_radio_buf,						// *SKY*
			old_mode,
			old_def_pb_state;

	string	pathname,
				old_action_func,
				old_def_pb;

	/*
	**		Save the current value of the global variable _dialog_data_buf.
	*/

	old_data_buf = _dialog_data_buf;

	/*
	**	If we are passed an existing buffer id, we use that buffer;
	**	if not, but we are passed the name of an existing file,
	**	we create the buffer.  If we get neither parameter, we
	**	fail.
	*/

	if (!get_parm (7, _dialog_data_buf) && get_parm (6, pathname))
		{
		/*
		**		If the file name is absolute (which we determine by
		**	the presence of special characters) we don't look for
		**	it in the BHELP directory.
		*/

		if (!search_string ("[:/\\\\]", pathname, NULL, TRUE))
			pathname = search_path (_dialog_dir, pathname);

		/*
		**		If the file exists and is not already a buffer in BRIEF, put it
		**	in a buffer.  Otherwise, use the file's buffer ID.
		*/

		if (exist (pathname) && !(_dialog_data_buf = inq_buffer (pathname)))
			{
			_dialog_data_buf = create_buffer ("Information", pathname, TRUE);
			created_buffer = TRUE;
			}
		}
	/*
	**		Make sure that we have a valid buffer id at this point (we
	**	don't care if both the pathname and buffer id were specified;
	**	we just want to have something to display).
	*/

	if (!_dialog_data_buf)
		{
		error ("Can't create dialog box.");
		_dialog_data_buf = old_data_buf;
		return (FALSE);
		}
	message ("Creating dialog box...");

	/*
	**		Here we save the old value of _dialog_action_func
	**	and increment the _dialog_level counter so recursive
	**	invocations will know about this one.	We also save
	**	the values of all the other global variables.
	*/

	_dialog_level++;
	old_action_func = _dialog_action_func;
	old_type = _dialog_type;
	old_row = _dialog_row;
	old_col = _dialog_col;
	old_size = _dialog_size;
	old_disp_buf = _dialog_disp_buf;
	old_radio_buf = _dialog_radio_buf;
	old_mode = _dialog_mode;
	old_def_pb_state = _dialog_pb_state;
	old_def_pb = _dialog_default_pb;

	_dialog_mode = DIALOG_DBOX_MODE;

	old_buf_id = inq_buffer ();

	/*
	**		Here we create a non-file system buffer which we
	**	can display in the new window.  This buffer will be
	**	formatted by the information in _dialog_data_buf.
	**	At this point, the pathname is no longer needed, so we
	**	put the string to new uses (getting the buffer name
	**	and later the window-bottom message).
	*/

	get_parm (4, pathname);
	_dialog_disp_buf = create_buffer (pathname, NULL, TRUE);

	get_parm (5, pathname);
	get_parm (8, _dialog_action_func);
	_dialog_size = (rx - lx) - 1;
	keyboard_push (_dialog_list_keymap);

	/*
	**		Create a non-file system buffer for managing radio buttons.
	*/

	_dialog_radio_buf = create_buffer ("RadioButtons", NULL, TRUE);	// *SKY*

	/*
	**		Create a non-file system buffer for managing mouse info.
	*/

	_dialog_mouse_buf = create_buffer ("MouseInfo", NULL, TRUE);	

	/*
	**		Now we call _format_dialog to design the display buffer
	**	and we create a window to put it in.  We postpone
	**	refreshing the display.
	*/

	_format_dialog ();
	set_ctrl_state( ZOOM_BTN, HIDE_CTRL);		// hide the zoom button for this window
	set_ctrl_state( HORZ_SCROLL, HIDE_CTRL);	// hide the horz scroll bar for this window
	set_ctrl_state( VERT_SCROLL, HIDE_CTRL);	// hide the vertical scroll bar for this window
	create_window (lx, by, rx, ty, pathname);
	attach_buffer (_dialog_disp_buf);
	set_ctrl_state( ZOOM_BTN, SHOW_CTRL);		// show the zoom button
	set_ctrl_state( HORZ_SCROLL, SHOW_CTRL);	// show the horz scroll bar 
	set_ctrl_state( VERT_SCROLL, SHOW_CTRL);	// show the vertical scroll bar for this window

	/*
	**		We save the old value of Use Tab Characters in the
	**	pathname variable, and the old Editing Mode in lx.
	*/

	lx = inq_mode ();
	pathname = use_tab_char ("n");

	/* assign a mouse handler for the dialog box */
	set_mouse_action( "mouse_dialog" );

	/*
	**		Call the action function to indicate we are done initializing.
	**	This is a good place for you to put your own keys into the
	**	keymap; by default, your key assignments will go into the
	**	global keymap for both lists and typed fields, but you may
	**	select one of the local keymaps and put assignments into it
	**	as long as you change back.  A typical use of this feature
	**	would be to install a help function by assigning it to Alt-h.
	**	Don't try to change the basic functioning of either the
	**	lists or the fields, however.
	*/

	message ("Dialog box created.");
	execute_macro (_dialog_action_func, DIALOG_INIT);
	refresh ();
	process ();

	execute_macro (_dialog_action_func, DIALOG_TERM);

	/*
	**		When we get here (after the user presses one of the three
	**	keys F10, Esc, and Grey -) we start cleaning up.  First
	**	we unassociate any current local keymap and pop the
	**	list keymap.  We preserve both keymaps for future use.
	*/

	use_local_keyboard (0);

	keyboard_pop (TRUE);

	/*
	**		Now we restore some modes.
	*/

	if (lx != inq_mode ())
		insert_mode ();

	use_tab_char (pathname);

	/*
	**		After processing the dialog box, we restore the
	**	previous state and return TRUE.	We only delete the
	**	information buffer if we created it and if it is
	**	not visible in any windows.  We delete the display
	**	buffer regardless.
	*/

	delete_window ();
	set_buffer (_dialog_data_buf);

	if (created_buffer && !inq_views ())
		delete_buffer (_dialog_data_buf);

	delete_buffer (_dialog_disp_buf);
	delete_buffer (_dialog_radio_buf);	// *SKY*

	delete_buffer (_dialog_mouse_buf);	
	set_buffer (old_buf_id);

	/*
	**		Decrement the dialog level and restore the values of the
	**	globals.  If the level becomes zero, we're leaving the
	**	dialog manager for good, so we re-run the new file
	**	macros and clear the message line.
	*/

	if (!--_dialog_level)
		{
		call_registered_macro (1);
		message ("");
		}
	_dialog_action_func = old_action_func;
	_dialog_type = old_type;
	_dialog_row = old_row;
	_dialog_col = old_col;
	_dialog_size = old_size;
	_dialog_disp_buf = old_disp_buf;
	_dialog_data_buf = old_data_buf;
	_dialog_radio_buf = old_radio_buf;	// *SKY*
	_dialog_mode = old_mode;
	_dialog_pb_state = old_def_pb_state;
	_dialog_default_pb = old_def_pb;
	returns (TRUE);
}

/*
**		_dialog_next_and_save:
**
**		Acts just like _dialog_next, but when invoked on the last field
**	in the dialog manager, simulates a press of F10.
*/

void _dialog_next_and_save ()
{
	/*
	**		If we can leave the current field (the input is valid),
	**	we look for another field to enter.  If there is none,
	**	we pretend F10 was pressed, and we signal to save the screen.
	*/

	if (_dialog_exit ())
		{
		set_buffer (_dialog_data_buf);
		end_of_line ();

		/*
		**		If we have no other field to go to, we pretend F10 was
		**	pressed; if it's OK to exit, we exit one process level.
		**	If we can't exit, we reenter the same field.  (If we
		**	have another field to begin with, we enter it.)
		*/

		if (!search_fwd (FIND_CONTROL, TRUE, FALSE))
			if (execute_macro (_dialog_action_func, DIALOG_F10))
				{
				_exit ();
				return;
				}
			else
				beginning_of_line ();

		_dialog_enter ();
		}
}

/*
**		_dialog_next, _dialog_prev, _dialog_home, _dialog_end:
**
**		These four macros move the highlight to another field and set
**	all the global variables for that field.	There is some redundant
**	code, because performance is fairly important.
*/

void _dialog_next ()
{
	if (_dialog_exit ())
		{
		set_buffer (_dialog_data_buf);

		/*
		**		Find the line on which the desired field is defined.
		*/

		end_of_line ();

		if (!search_fwd (FIND_CONTROL, TRUE, FALSE))
			beginning_of_line ();

		_dialog_enter ();
		}
}

void _dialog_prev ()
{
	if (_dialog_exit ())
		{
		set_buffer (_dialog_data_buf);

		if (!(up () && search_back (FIND_CONTROL, TRUE, FALSE)))
		{
			top_of_buffer ();
			search_fwd (FIND_CONTROL, TRUE, FALSE);
		}

		_dialog_enter ();
		}
}

void _dialog_home ()
{
	if (_dialog_exit ())
		{
		set_buffer (_dialog_data_buf);
		top_of_buffer ();
		search_fwd (FIND_CONTROL, TRUE, FALSE);
		_dialog_enter ();
		}
}

void _dialog_end ()
{
	if (_dialog_exit ())
		{
		set_buffer (_dialog_data_buf);
		end_of_buffer ();
		search_back (FIND_CONTROL, TRUE, FALSE);
		_dialog_enter ();
		}
}

/*
**		_dialog_set_globals:
**
**		This macro sets the global variables _dialog_type, _dialog_row,
**	and _dialog_col by reading information from the data buffer.  It
**	returns the line read.
*/

string _dialog_set_globals ()
{
	string	line;

	/*
	**		We require that the row and column numbers follow
	**	the first ( and , in the string, respectively,
	**	although there may be whitespace in between.  The
	**	only two double quotes in the string must surround
	**	the text of the field.
	*/

	_dialog_type = index (CONTROL_TYPES, upper (substr (ltrim (line = read ()), 1, 1)));
	_dialog_row = atoi (substr (line, index (line, "(") + 1));
	_dialog_col = atoi (substr (line, index (line, ",") + 1));
	returns (line);
}

/*
**		_dialog_enter:
**
**		Sets up to enter a list or a field based on the data buffer
**	and global variables.
*/

void _dialog_enter ()
{
	int		offset,
				len;
	string	field;

	_dialog_set_globals ();
	set_buffer (_dialog_disp_buf);
	move_abs (_dialog_row, _dialog_col);

	if (_dialog_type == PUSH && _dialog_pb_state)
		_dlg_select_push (_dialog_default_pb, 0);
	else if (!_dialog_pb_state && _dialog_type != PUSH)
		_dlg_select_push (_dialog_default_pb, 1); /* show defaul pb if it exists */

	/*
	**		Perform the appropriate initializations and highlighting based on
	**	the control type.
	*/

	switch (_dialog_type)
		{
		case LIST:
			{

			/*
			**		Adjust _dialog_col to compensate for a possible initial tab.
			*/

			insert_mode (TRUE);
			_dialog_col--;
			prev_char ();
			field = read ();
			len = (index (field, ")") - (offset = index (field, "("))) - 1;
			move_rel (0, offset - 1);
			delete_char ();
			insert ("\t");
			move_rel (0, len);
			delete_char ();
			insert ("\t");
			move_rel (0, -2);
			drop_anchor ();
			move_rel (0, 1 - len);
			execute_macro (_dialog_action_func, DIALOG_ENTER_LIST, _dialog_row, substr (field, ++offset, len));
			}

		case CHECK:
		case PUSH:
		case RADIO:
			{
			/*	 
			**		Position the cursor in the check box or button.
			*/

			use_local_keyboard (_dialog_button_keymap);

			_dialog_col++;
			next_char ();

			if (_dialog_type == CHECK)
				execute_macro (_dialog_action_func, DIALOG_ENTER_CHECK, _dialog_row, _dlg_check_btn ());
			else if (_dialog_type == PUSH)
				execute_macro (_dialog_action_func, DIALOG_ENTER_PUSH, _dialog_row, _dlg_push_btn ());
			else
				execute_macro (_dialog_action_func, DIALOG_ENTER_RADIO, _dialog_row, _dlg_radio_btn ());
			}

		default:
			{

			/*
			**		Highlight the field and call the action function.
			*/

			use_local_keyboard (_dialog_field_keymap);

			end_of_line ();
			drop_anchor ();
			move_abs (0, _dialog_col);
			execute_macro (_dialog_action_func, DIALOG_ENTER_FIELD, _dialog_row, _field_contents ());
			}
		}
}

/*
**		_dialog_exit:
**
**		This routine is called whenever the user is leaving a field or
**	a list.	First, if the user is leaving a typed field, it calls the
**	built-in data validation routine for that type.  If the data is
**	invalid, it prints an error message and returns FALSE.  If the data
**	is valid, it calls the action function with either the list or field
**	parameter and the value of the list or field.  If the action function
**	returns TRUE (i.e. the field's contents were still valid), it
**	removes the highlight (if any), and unassociates the current local
**	keymap.	Returns what the action function returned.
*/

int _dialog_exit ()
{
	int	retval;

	switch (_dialog_type)
		{
		case LIST:
			{
			/*
			**		Note that in the execute_macro call below, if the action
			**	function never gets its last parameter, _list_button
			**	never gets called.
			*/

			if (retval = execute_macro (_dialog_action_func, DIALOG_EXIT_LIST, _dialog_row, _list_button ()))
				{
				/*
				**		Change the highlight back to parens.
				*/

				raise_anchor ();
				prev_char ();
				translate ("\\t{*}\\t", "(\\0)", FALSE, TRUE);
				}
			}

		case CHECK:
			{
			if (retval = execute_macro (_dialog_action_func, DIALOG_EXIT_CHECK, _dialog_row, _dlg_check_btn ()))
				use_local_keyboard (0);
			}

		case PUSH:
			{
			if (retval = execute_macro (_dialog_action_func, DIALOG_EXIT_PUSH, _dialog_row, _dlg_push_btn ()))
				use_local_keyboard (0);
			}

		case RADIO:
			{
			if (retval = execute_macro (_dialog_action_func, DIALOG_EXIT_RADIO, _dialog_row, _dlg_radio_btn ()))
				use_local_keyboard (0);
			}

		default:
			{
			string	value = _field_contents ();

			if (retval = _dialog_validate (value) && execute_macro (_dialog_action_func, DIALOG_EXIT_FIELD, _dialog_row, value))
				{
				raise_anchor ();
				use_local_keyboard (0);
	
				message ("");
				}
			}
		}
	returns (retval);
}

/*
**		_invalid_key:
**
**		For dialog boxes, if we are in a list, calls _list_go.  Otherwise,
**	calls _field_insert.  For menus, calls _menu_go.  For other cases,
**	does nothing.
*/

replacement void _invalid_key ()
{
	int	key = read_char ();

	if ((key & 0xff) && (key & 0xff) < 128
				&& _dialog_mode
				&& (_dialog_data_buf == inq_buffer () || _dialog_disp_buf == inq_buffer ()))
		{
		if (_dialog_mode == DIALOG_MENU_MODE)
			_menu_go (key);
		else if (_dialog_type == LIST)
			_list_go (key);
		else if (_dialog_type == CHECK || _dialog_type == PUSH || _dialog_type == RADIO)
			beep ();
		else
			_field_insert (key);

		refresh ();
		}
	else
		{
		push_back (key);
		_invalid_key ();
		}
}

/*
**		_format_menu:
**
**		Automatically centers each line of the menu buffer in the
**	window, and moves the rest of the line out of sight.	Normally not
**	done, since it's faster if you format your own menus.
*/

void _format_menu ()
{
	int	button_offset,
			button_length;

	string	button,
				text;

	top_of_buffer ();

	while (!inq_position ())
		{
		button = _menu_button ();
		button_offset = index (button, ";");
		text = ltrim (substr (button, button_offset + 1));
		button = trim (substr (button, 1, button_offset - 1));
		button_length = strlen (button);
		button_offset = (_dialog_size - button_length) / 2 + 1;

		delete_to_eol ();
		move_abs (0, button_offset);
		insert (button);
		move_abs (0, _dialog_size + 1);
		insert (";" + text);
		beginning_of_line ();
		down ();
		}
}

/*
**		_process_menu:
**
**		Creates and processes a menu, calling the user when interesting
**	events occur.
*/

int _process_menu (int lx, int by, int rx, int ty, string, string, ~string, ~int, string, ~int, ~int, ~string)
{
	int	old_buf_id,
			old_data_buf_id,
			old_size,
			old_mode,
			menu_buf_id,
			created_buffer,
			fast_mode,
			screen_x,
			screen_y,
			retval;

	string	pathname,
				old_action_func;


	extern void mouse_menu();

	/*
	**		If we are passed an existing buffer id, we use that buffer;
	**	if not, but we are passed the name of an existing file,
	**	we create the buffer.  If we get neither parameter, we
	**	fail.
	*/

	if (!get_parm (7, menu_buf_id) && get_parm (6, pathname))
		{
		/*
		**		If the file name is absolute (which we determine by
		**	the presence of special characters) we don't look for
		**	it in the BHELP directory.
		*/

		if (!search_string ("[:/\\\\]", pathname, NULL, TRUE))
			pathname = search_path (_dialog_dir, pathname);

		/*
		**		If the file exists already, we put it in a buffer.
		**	Note that we use a spare string variable, old_
		**	action_func, to get the buffer name parameter.
		*/

		if (exist (pathname))
			{
			get_parm (4, old_action_func);
			menu_buf_id = create_buffer (old_action_func, pathname, TRUE);
			created_buffer = TRUE;
			}
		}
	/*
	**		We have a menu buffer ID if we got one as a parameter or if
	**	create_buffer above succeeded.  If we have one, we can create
	**	the menu.  If not, we return immediately so that the
	**	ensuing code does not have to be part of the if statement;
	**	this saves precious stack space, allowing for more nesting.
	*/

	if (!menu_buf_id)
		{
		error ("Can't create menu.");
		return (FALSE);
		}
	old_buf_id = inq_buffer ();
	message ("Creating menu...");

	/*
	**		Here we save the old value of _dialog_action_func
	**	and increment the _dialog_level counter so recursive
	**	invocations will know about this one.
	*/

	_dialog_level++;
	_dialog_menu_prefix = "";
	old_action_func = _dialog_action_func;
	old_mode = _dialog_mode;
	old_data_buf_id = _dialog_data_buf;
	_dialog_mode = DIALOG_MENU_MODE;

	/*
	**		Now that we've created the menu buffer, we call the
	**	menu creation event in case we want to do any raw
	**	processing on the menu (like adding our own buttons).
	**	For this event, the menu buffer is guaranteed current.
	**	We pass the buffer name to the action function, reusing
	**	a string variable again.
	*/

	get_parm (8, _dialog_action_func);
	set_buffer (_dialog_data_buf = menu_buf_id);
	inq_names (NULL, NULL, pathname);
	execute_macro (_dialog_action_func, DIALOG_CREATE_MENU, NULL, pathname);

	/*
	**		We reuse pathname to hold the bottom line message.
	**	Note that if lx == rx or by == ty, we have to calculate
	**	rx (width) and by (height) automatically.
	*/

	get_parm (5, pathname);

	/*
	**		If either window dimension is zero, we size the window
	**	automatically.  It has to be at least 1 line x 14 cols
	**	(larger if a long bottom line message is to be displayed).
	*/

	if (lx == rx || ty == by)
		{
		top_of_buffer ();

		/*
		**		Use the first semicolon, if any, and the number
		**	of lines to get the optimum size.  (No semicolon:
		**	use a 14-column window.)
		*/

		if (search_fwd (";", FALSE))
			inq_position (NULL, rx);
		else
			rx = 15;

		rx += lx;
		end_of_buffer ();
		inq_position (by);
		by += ty + 1;
		top_of_buffer ();

		/*
		**		Make sure the window will fit on the screen;
		**	if it won't, shrink it.  Leave room for the
		**	"shadows".
		*/

		inq_screen_size (screen_y, screen_x);
		screen_y -= 4;
		screen_x -= 3;

		if (screen_y < by)
			by = screen_y;

		if (screen_x < rx)
			rx = screen_x;
		}
	/*
	**		If either dimension is too small, display an error
	**	message and not the window.  Note that we duplicate
	**	all the "cleanup" code here so we can return
	**	immediately.  That's because the code size is less
	**	crucial than the maximum nesting depth here, since
	**	the macro is intended to be called recursively and
	**	nesting in macros consumes a ton of stack space.
	*/

	screen_x = strlen (pathname) + 2;
	screen_y = rx - lx;

	if ((by - ty <= 1 || screen_y <= 14) || screen_y <= screen_x)
		{
		error ("Window would be too small.");

		if (created_buffer && !inq_views ())
			delete_buffer (menu_buf_id);

		set_buffer (old_buf_id);

		if (!--_dialog_level)
			{
			call_registered_macro (1);
			message ("");
			}
		_dialog_action_func = old_action_func;
		_dialog_mode = old_mode;
		_dialog_data_buf = old_data_buf_id;
		return (FALSE);
		}
	_dialog_picked = 0;
	old_size = _dialog_size;
	_dialog_size = (rx - lx) - 1;
	keyboard_push (_dialog_menu_keymap);

	/*
	**		If we were passed a tenth parameter, and it was TRUE,
	**	we change to "fast" mode (meaning the window is already
	**	formatted--centered, if desired, and no visible semicolons).
	*/

	if (!(get_parm (9, fast_mode) && fast_mode))
		_format_menu ();

	/*
	**		Now we display the menu window, but we postpone refreshing
	**	the display until we highlight the first button.
	*/

	set_ctrl_state( ZOOM_BTN, HIDE_CTRL);		// hide the zoom button for this window
	set_ctrl_state( HORZ_SCROLL, HIDE_CTRL);	// hide the horz scroll bar for this window
	create_window (lx, by, rx, ty, pathname);
	set_ctrl_state( ZOOM_BTN, SHOW_CTRL);		// show the zoom button
	set_ctrl_state( HORZ_SCROLL, SHOW_CTRL);	// show the horz scroll bar 
	attach_buffer (menu_buf_id);
	message ("Menu created.");

	/*
	**	Establish an action routine for menu mouse events.
	**	Note: the mouse action routine is set BEFORE the
	**	DIALOG_INIT action function is called to allow the default
	**	mouse event handler to be sub-classed when processing
	** the DIALOG_INIT message.
	*/
	set_mouse_action( "mouse_menu" );

	/*
	**		Go to the top of the buffer, call the init event,
	**	highlight the first selectable line, redraw the screen,
	**	and go into interactive mode.
	*/

	top_of_buffer ();
	execute_macro (_dialog_action_func, DIALOG_INIT);
	_dialog_menu_home ();
	refresh ();

	process ();

	/*
	**		Remove the highlight, then the keyboard, then the window.
	*/

	raise_anchor ();
	execute_macro (_dialog_action_func, DIALOG_TERM);
	keyboard_pop (TRUE);
	delete_window ();

	/*
	**		Here we set parameter 10 to indicate the button number picked,
	**	set parameter 11 to the text of the picked button, and clear
	**	clear _dialog_picked so that nested menus work properly.
	**
	**		A zero value for parameter 10 indicates the user pressed <Esc>
	**	or <Keypad minus> to abort the menu.
	**
	**		A nonzero value indicates the user pressed <Enter> to select
	**	a button, and the button number becomes the parameter value.
	**	The button text is in parameter 11.
	*/

	put_parm (10, _dialog_picked);
	put_parm (11, _dialog_picked ? _menu_button () : "");
	_dialog_picked = 0;

	/*
	**		Restore the old current buffer and delete the menu
	**	buffer, if we created the buffer ourselves and
	**	it's not visible in any window.
	*/

	if (created_buffer && !inq_views ())
		delete_buffer (menu_buf_id);

	set_buffer (old_buf_id);

	/*
	**		Decrement the menu level.	If it is zero,
	**	we're leaving the menu package for good--so re-run the
	**	new file macros, and clear the message line.
	*/

	if (!--_dialog_level)
		{
		call_registered_macro (1);
		message ("");
		}
	_dialog_action_func = old_action_func;
	_dialog_mode = old_mode;
	_dialog_size = old_size;
	_dialog_data_buf = old_data_buf_id;
	returns (TRUE);
}

/*
**		For invalid keys.
*/

void _dlg_beep (void)
{
	beep ();
}

