/**********************************************************************/
/*                                                                    */
/*	CRISP - Programmable editor                                   */
/*	===========================                                   */
/*                                                                    */
/*  File:          select.cr                                          */
/*  Author:        P. D. Fox                                          */
/*  Created:       14 Jun 1991                     		      */
/*                                                                    */
/*  Copyright (c) 1990, 1991 Paul Fox                                 */
/*                All Rights Reserved.                                */
/*                                                                    */
/*                                                                    */
/*--------------------------------------------------------------------*/
/*   Description:  Selection  macros  to  implement  the  popup user  */
/*   interface.							      */
/*                                                                    */
/**********************************************************************/

/* SCCS ID: %Z% %M% %R%.%L% */

# include	"crisp.h"

/**********************************************************************/
/*   Line where we put the first popup.				      */
/**********************************************************************/
# define 	TOP_LINE	1
/**********************************************************************/
/*   Number  of  lines  further  down screen between each successive  */
/*   popup.							      */
/**********************************************************************/
# define	TOP_LINE_INCR	1

/**********************************************************************/
/*   Offset  from  from  right hand margin for each successive popup  */
/*   window.							      */
/**********************************************************************/
# define 	WINDOW_OFFSET	6

# define 	MARGIN		12

extern int	popup_level;
int	top_line = TOP_LINE;
int	window_offset = WINDOW_OFFSET;
/**********************************************************************/
/*   top_keyboard is the top level keyboard map.		      */
/**********************************************************************/
extern int top_keyboard;

/**********************************************************************/
/*   Display  list  of  buffers  on screen, and allow user to make a  */
/*   selection.							      */
/*   								      */
/*   First  parameter  says  whether  to  display  in  long or short  */
/*   format.  Short  format  is  compatible  with the BRIEF display;  */
/*   Long  mode  is  adds extra status fields, demonstrating CRISP's  */
/*   enhancements.						      */
/*   								      */
/*   Second  parameter  says  whether  to  display system buffers as  */
/*   well.							      */
/**********************************************************************/
void
buffer_list(int shortmode, int sysbuffers)
{
	int	curbuf;
	int	buf_no = 1;
	string	buf_name;
	int	buffer_list;
	int	win, cline, ccol, i;
	int	retval;
	int	this_buf;
	int	position;
	string	file_name;
	string	tmp, line, modes;

	shortmode = !shortmode;

	curbuf = inq_buffer();
	inq_position(cline, ccol);
	buffer_list = create_buffer("Buffer List", NULL, 1);
	set_buffer(buffer_list);

	set_buffer(curbuf);
	set_buffer (next_buffer());
	while (1) {
		inq_names(file_name, NULL, buf_name);
		this_buf = inq_buffer();
		if (sysbuffers || (this_buf != inq_scrap() && !inq_system())) {
			if (shortmode)
	 			sprintf(tmp, "%-14s %s%s",
					buf_name,
					file_name,
					inq_modified() ? "*" : "");
			
			else {
				inq_position(position);
				modes = "";
				modes += inq_modified() ? "*" : " ";
				modes += inq_buffer_flags() & BF_PROCESS ? "P" : " ";
				modes += inq_buffer_flags() & BF_BACKUP ? "B" : " ";
				modes += inq_buffer_flags() & BF_READONLY ? "R" : " ";
				modes += inq_system() ? "S" : " ";
				modes += inq_buffer_flags() & BF_BINARY ? " <Bin> " : 
					 inq_buffer_flags() & BF_CR_MODE? " <Dos> " :
					 "       ";
	 			sprintf(tmp, "%-14s  %5d %5d %s %s",
					buf_name, inq_lines(), position, modes, file_name);
				}
			set_buffer(buffer_list);
			if (buf_no > 1)
				insert("\n");
			insert(tmp);
			++ buf_no;
			set_buffer(this_buf);
			}
		if (inq_buffer() == curbuf)
			break;
			
		/***********************************************/
		/*   Switch to next buffer.		       */
		/***********************************************/
		while (1) {
			i = next_buffer(1);
			set_buffer(i);
			if ((inq_buffer_flags() & BF_SYSBUF) == 0
			    || sysbuffers || i == curbuf)
				break;
			}
		}

	message("%d buffer%s in list.", buf_no - 1, buf_no == 1 ? "" : "s");

	i = inq_line_length(buffer_list);
	if (i < 68)
		i = 68;
	win = sized_window(buf_no, i,
		"<Up>, <Down> to move. <Enter> to select, D to delete, W to write");
	set_buffer(buffer_list);
	sort_buffer();
	set_buffer(curbuf);
	retval = select_buffer(buffer_list, win, SEL_NORMAL,
		buffer_list_keys(),
		NULL,
		"Buffer List"
		);
	message("");
	
	if (retval < 0) {
		delete_buffer(buffer_list);
		set_buffer(curbuf);
		attach_buffer(curbuf);
		move_abs(cline, ccol);
		return;
		}

	set_buffer(buffer_list);
	move_abs(retval, 0);
	line = trim (read());
	delete_buffer(buffer_list);
	set_buffer(curbuf);
	move_abs(cline, ccol);

	line = substr(line, rindex(line, " ") + 1);
	if (substr(line, strlen(line)) == "*")
		line = substr(line, strlen(line) - 1);
	if (line != "")
		edit_file(line);
}
void
buffer_list_keys()
{
	assign_to_key("d", "buf_delete");
	assign_to_key("D", "buf_delete");
	assign_to_key("w", "buf_write");
	assign_to_key("W", "buf_write");
}
void
buf_delete()
{

	string	line, str;
	int	buf;

	line = trim (read());
	line = substr(line, rindex(line, " ") + 1);
	if (substr(line, strlen(line)) == "*")
		line = substr(line, strlen(line) - 1);

	buf = inq_buffer(line);
	/*--------------------------------------------------
	/*   Dont let user delete a buffer which is currently
	/*   being displayed.
	/*--------------------------------------------------*/
	if (inq_views(buf)) {
		error("Cannot delete a buffer being displayed.");
		return;
		}
	/*--------------------------------------------------
	/*   If buffer has been modified, check whether user
	/*   is really sure.
	/*--------------------------------------------------*/
	if (inq_modified(buf)) {
		str = "X";
		while (str != "y" && str != "Y") {
			if (!get_parm(NULL, str, "Buffer has not been saved. Delete [ynw]? ", 1))
				str = "n";
			if (str == "n" || str == "N") {
				message("");
				return;
				}
			if (str == "w" || str == "W") {
				int curbuf;
				curbuf = inq_buffer();
				set_buffer(buf);
				write_buffer();
				set_buffer(curbuf);
				break;
				}
			}
		}
	delete_buffer(buf);
	delete_line();
}
void
buf_write()
{	string line;
	int	curbuf, buf;

	line = trim (read());
	line = substr(line, rindex(line, " ") + 1);
	if (substr(line, strlen(line)) == "*")
		line = substr(line, strlen(line) - 1);

	buf = inq_buffer(line);
	if (!inq_modified(buf)) {
		error("Buffer already saved.");
		return;
		}
	curbuf = inq_buffer();
	set_buffer(buf);
	write_buffer();
	set_buffer(curbuf);
	re_translate(SF_NOT_REGEXP, "*", " ");
	beginning_of_line();
	message("Saved buffer: %s", line);
}
string
select_file(string wild_card, string title, int dirs)
{	string	file, path, cwd;
	int	i;

	getwd(NULL, cwd);
	if (wild_card == "")
		wild_card = "*";
	else
		wild_card += "*";
	if (i = rindex(wild_card, "/")) {
		path = substr(wild_card, 1, i - 1);
		if (path == "")
			cd("/");
		else
			cd(path);
		}
	while (1) {
		getwd(NULL, path);
		file = _select_file(path, wild_card, title, dirs);
		/***********************************************/
		/*   Accept  the  'entry'  if we were aborted. */
		/***********************************************/
		if (file == "")
			break;
		/***********************************************/
		/*   If  user  hasn't  selected  a  directory  */
		/*   then accept the entry.		       */
		/***********************************************/
		if (substr(file, strlen (file)) != "/")
			break;
		/***********************************************/
		/*   User  selected  a directory so lets show  */
		/*   him that one.			       */
		/***********************************************/
		cd(file);
		wild_card = "*";
		}
	refresh();
	cd(cwd);
	/***********************************************/
	/*   If  user  specified  a  file,  force the  */
	/*   prompt to accept the line.		       */
	/***********************************************/
	if (file != "")
		push_back(key_to_int("<Enter>"));
	return path + "/" + file;
}
string
_select_file(string path, string wild_card, string title, int dirs)
{
	string	name,
		file,
		nl,
		tmpbuf;
	int	size,
		ret,
		mtime,
		mode,
		curbuf,
		width,
		min_width,
		buf,
		win;
	extern string CRISP_SLASH;

	curbuf = inq_buffer();
	min_width = strlen(path) + 6;
	buf = create_buffer(title != "" ? title : path, NULL, 1);
	set_buffer(buf);

	if (wild_card == "" || wild_card == "*" || 
	    substr(wild_card, strlen(wild_card) - 1, 1) == CRISP_SLASH) {
		insert("../");
		nl = "\n";
		wild_card = "*";
		}

	file_pattern(wild_card);
	
	while (find_file(name, size, mtime, mtime, mode)) {
		/***********************************************/
		/*   If  we're  only  selecting  directories,  */
		/*   then ignore files.			       */
		/***********************************************/
		if (dirs && (mode & S_IFDIR) == 0)
			continue;
		sprintf(tmpbuf, "%s%s", name, 
			mode & S_IFDIR ? "/" : "");
		insert(nl);
		nl = "\n";
		insert(tmpbuf);
		}

	file = "";
	if (inq_lines() == 0)
		beep();
	else {
		width = inq_line_length();
		if (width < min_width)
			width = min_width;
		sort_buffer();
		end_of_buffer();
		insert("\n");
		window_offset += 20;
		win = sized_window(inq_lines(), width, "");
		window_offset -= 20;
		ret = select_buffer(buf, win, NULL, NULL, NULL,
			"help_display \"features/abbfile.hlp\" \"Filename Abbreviations\"");
		if (ret >= 0) {
			goto_line(ret);
			file = trim(read());
			}
		}
	delete_buffer(buf);
	set_buffer(curbuf);
	attach_buffer(curbuf);
	return file;
}
/**********************************************************************/
/*   Allows  current  buffer  to  be  edited and we return when <Esc> */
/*   key is hit.						      */
/**********************************************************************/
void
select_editable()
{
	string	old_escape;
		
	keyboard_push(top_keyboard);
	old_escape = inq_assignment("<Esc>");
	assign_to_key("<Esc>", "exit");
	process();
	assign_to_key("<Esc>", old_escape);
	keyboard_pop(TRUE);
}
# define	SEL_SELECTED	1	/* Enter/Space typed.	*/
# define	SEL_ABORTED	2	/* Escape typed.	*/
/***********************************************************************
/*   Usage:
/*        (select_list  title
/*                      message_string
/*                      step
/*                      list
/*			flags
/*			[help_string]
/*			do_list		-- Private key bindings.
/*                      )
/*
/***********************************************************************/
int
select_list(string title, string message_string, int step, declare l, int flags, declare help_var, list do_list)
{
	int	depth, width;
	list	help_list;
	int	buffer,
		old_buffer,
		win,
		old_win,
		retval;
	declare option;

	if (!is_list(l))
		return -1;

	/***********************************************/
	/*   Save  position  so  that  cursor doesn't  */
	/*   move  to  the top of the screen when the  */
	/*   user has finished selecting.	       */
	/***********************************************/
	save_position();

	old_buffer = inq_buffer();
	buffer = create_buffer(title, NULL, 1);
	set_buffer(buffer);
	width = strlen(title) + 4;
	
	option = "";
	while (1) {
		option = l[step * depth];
		if (!is_string(option))
			break;
		if (step > 1) {
			do_list[depth] = l[step*depth + 1];
			if (step > 2)
				help_list[depth] = l[step*depth + 2];
			}
		if (depth > 0)
			insert("\n");
		insert(option);
		++ depth;
		}
	
	width = inq_line_length();
	if (width < strlen(title))
		width = strlen(title) + 3;
			
	old_win = inq_window();
	win = sized_window(inq_lines() + 1, 
		width + (flags == SEL_CENTER ? 2 : 1),
		message_string);
	top_line += TOP_LINE_INCR;
	window_offset += 6;
	
	if (!is_string(help_var))
		help_var = help_list;
	retval = select_buffer(buffer, win, flags, NULL, do_list, help_var);
	
	top_line -= TOP_LINE_INCR;
	window_offset -= 6;
	
	delete_buffer(buffer);
	set_buffer(old_buffer);
	set_window(old_win);
	attach_buffer(old_buffer);
	
	/***********************************************/
	/*   Restore  position  --  leaves  cursor in  */
	/*   same   place  on  screen  as  when  user  */
	/*   entered this macro.		       */
	/***********************************************/
	restore_position();

	return retval;
}
/**********************************************************************/
/*   This  function  is  similar to select_list() but we assume that  */
/*   the  list  is  unstructured  --  there  are  no actions or help  */
/*   associated with each element.				      */
/**********************************************************************/
int
select_slim_list(string title, string message_string, declare l, int flags, declare help_var, string do_list)
{
	int	width;
	list	help_list;
	int	buffer,
		old_buffer,
		win, len,
		old_win, i,
		retval;

	if (!is_list(l))
		return -1;

	/***********************************************/
	/*   Save  position  so  that  cursor doesn't  */
	/*   move  to  the top of the screen when the  */
	/*   user has finished selecting.	       */
	/***********************************************/
	save_position();

	old_buffer = inq_buffer();
	buffer = create_buffer(title, NULL, 1);
	set_buffer(buffer);
	width = strlen(title) + 4;

	len = length_of_list(l);
	for (i = 0; i < len; )
		insert(l[i++] + "\n");
	delete_line();
	
	width = inq_line_length();
	if (width < strlen(title))
		width = strlen(title) + 3;
			
	old_win = inq_window();
	win = sized_window(inq_lines() + 1, 
		width + (flags == SEL_CENTER ? 2 : 1),
		message_string);
	top_line += TOP_LINE_INCR;
	window_offset += 6;
	
	if (!is_string(help_var))
		help_var = help_list;
	retval = select_buffer(buffer, win, flags, do_list, NULL, help_var);
	
	top_line -= TOP_LINE_INCR;
	window_offset -= 6;
	
	delete_buffer(buffer);
	set_buffer(old_buffer);
	set_window(old_win);
	attach_buffer(old_buffer);
	
	/***********************************************/
	/*   Restore  position  --  leaves  cursor in  */
	/*   same   place  on  screen  as  when  user  */
	/*   entered this macro.		       */
	/***********************************************/
	restore_position();

	return retval;
}
/***********************************************************************
/*   Macro to select an entry from a buffer (passed as a parameter).
/*   The cursor keys and <Home>, <End> allow movement; typing a letter
/*   looks for a line which matches the character typed.
/*
/*   This macro returns a number indicating the line in the buffer
/*   selected, or < 0 to indicate the user pressed <Esc> to abort
/*   the selection.
/*
/*	(macro select_buffer
/*		buf		Buffer to make selection from
/*		win		Window in which to display buffer.
/*		flags		Flags:
/*				  SEL_NORMAL
/*				  SEL_CENTER -- center lines in window.
/*				  SEL_TOP_OF_WINDOW -- make indicated line top
/*						of window.
/*		keylist		List of commands to execute to set up local
/*				keyboard definitions.
/*		[do_list]	Action to perform if item is selected (<Enter>).
/*		[help_list]	List of macros to call if <Alt-H> typed on menu
/*				item.
/*		[start_line]
/*		[keep_window]
/*		)
/***********************************************************************/
int
select_buffer(int buf, int win, ~int, ~list, list do_list, declare help_list, ~int, int keep_window)
{	int 	old_buf,
		old_win,
		number_of_items,
		selection,
		flags = 0,
		width,
		depth,
		retval,
		start_line;
	int	sel_warpable;
	declare	keylist;

	/***********************************************/
	/*   If  this  variable is TRUE and the mouse  */
	/*   clicks   on  an  entry  then  the  mouse  */
	/*   macro  will  call  sel_warp() to get the  */
	/*   highlight    drawn    on    the   winodw  */
	/*   properly.   We   use  the  dynamic  name  */
	/*   resolution  so  that the mouse macro can  */
	/*   test this flag.			       */
	/***********************************************/
	sel_warpable = TRUE;

	old_buf = inq_buffer();
	old_win = inq_window();

	/***********************************************/
	/*   If  caller  didn't  specify  a  line  to  */
	/*   start  on,  then  hilight the first line  */
	/*   of the buffer when the popup appears.     */
	/***********************************************/
	if (get_parm(6, start_line) <= 0)
		start_line = 1;

	set_window(win);
	set_buffer(buf);
	attach_buffer(buf);
	top_of_buffer();
	number_of_items = inq_lines();
	
	get_parm(2, flags);
	if (flags & SEL_CENTER) {
	 	depth = 0;
		width = inq_line_length();
		while (++ depth <= number_of_items) {
			insert(" ", (width - strlen(read())) / 2 + 1);
			down();
			beginning_of_line();
			}
		}
	
	top_of_buffer();
	-- number_of_items;
	
	keyboard_push();
	keyboard_typeables();
	
	copy_keyboard(top_keyboard,
		"search__fwd",
		"search__back",
		"search_next");
	assign_to_key("<Alt-C>", "sel_copy");
	assign_to_key("<Alt-D>", "screen_dump");
	assign_to_key("<Alt-H>", "select_help");
	assign_to_key("<Home>", "sel_help");
	assign_to_key("<End>", "sel_end");
	assign_to_key("<Up>", "sel_up");
	assign_to_key("<Down>", "sel_down");
	assign_to_key("<Esc>", "sel_esc");
	assign_to_key("<Keypad-minus>", "sel_minus");
	assign_to_key("<Alt-K>", "sel_keys");
	assign_to_key("<Alt-P>", "screen_dump");

	/***********************************************/
	/*   If  we  have  a  mouse  then  set up the  */
	/*   mouse key definitions.		       */
	/***********************************************/
	if (display_mode() & DC_WINDOW)
		define_mouse_buttons();

	if (length_of_list (do_list)) {
		assign_to_key("<Space>", "sel_list");
		assign_to_key("<Enter>", "sel_list");
		}
	else {
		assign_to_key("<Space>", "exit");
		assign_to_key("<Enter>", "exit");
		}
	assign_to_key("<PgUp>", "sel_pgup");
	assign_to_key("<PgDn>", "sel_pgdn");
		
	goto_line(start_line);

	if (flags & SEL_TOP_OF_WINDOW)
		set_top_of_window();
	drop_anchor(3);
	
	/***********************************************/
	/*   Evaluate  function  setting  up  private  */
	/*   key-bindings.   If  function  returns  a  */
	/*   list,  save  that  in case user wants to  */
	/*   see key-bindings.			       */
	/***********************************************/
	get_parm(3, keylist);
	if (is_string(keylist))
		execute_macro(keylist);
	
	/***********************************************/
	/*   Intercept  all  normal  keys typed so we  */
	/*   can  do  the  selection of an item which  */
	/*   starts with a letter.		       */
	/***********************************************/
	register_macro(REG_TYPED, "sel_alpha", TRUE);
	
	/***********************************************/
	/*   Keep  track  of  the  level  of nesting.  */
	/*   This   allows   the   mouse  handler  to  */
	/*   decide what to do inside popups.	       */
	/***********************************************/
	popup_level++;
	process();
	popup_level--;

	if (selection == SEL_ABORTED)
		retval = -1;
	else
		inq_position(retval);

	/***********************************************/
	/*   Remove registered macro.		       */
	/***********************************************/
	unregister_macro(REG_TYPED, "sel_alpha", TRUE);
	raise_anchor();
	keyboard_pop();
						  
	if (! keep_window)
		delete_window();
	set_window(old_win);
	set_buffer(old_buf);
	attach_buffer(old_buf);
	
	return retval;
}
/**********************************************************************/
/*   This  macro  is  called  when  the user hits <Alt-C> in a popup  */
/*   window.  The  whole  window is copied to the scrap to allow the  */
/*   user to do what he/she wants with it.			      */
/**********************************************************************/
void
sel_copy()
{
	save_position();
	top_of_buffer();
	drop_anchor(MK_LINE);
	end_of_buffer();
	copy();
	restore_position();
	message("Buffer copied to scrap.");
}
/**********************************************************************/
/*   Function  called  if  user  types  <Alt-K>  to  see  the  macro  */
/*   specific key-bindings.					      */
/**********************************************************************/
void
sel_keys()
{	extern declare keylist;

	select_list("Key Bindings", "", 1, keylist, SEL_NORMAL);
}
void
sel_help()
{
	raise_anchor();
	move_abs(1, 0);
	drop_anchor(3);
}
void
sel_end()
{
	raise_anchor();
	move_abs(inq_lines(), 0);
	drop_anchor(3);
	refresh();
	set_bottom_of_window();
}
void
sel_up()
{
	raise_anchor();
	up();
	drop_anchor(3);
}
void
sel_down()
{
	int	current_item;
	extern int number_of_items;
	
	inq_position(current_item);
	if (current_item <= number_of_items) {
		raise_anchor();
		down();
		drop_anchor(3);
		}
}
void
sel_esc()
{	extern int selection;

	selection = SEL_ABORTED;
	exit();
}
void
sel_minus()
{	extern int selection;

	selection = SEL_ABORTED;
	exit();
}
void
sel_pgup()
{
	raise_anchor();
	page_up();
	drop_anchor(3);
}
void
sel_pgdn()
{	int line;
	raise_anchor();
	page_down();
	inq_position(line);
	if (line > inq_lines())
		goto_line (inq_lines());
	drop_anchor(3);
}
void
select_help()
{	int	line;
	extern declare help_list;

	inq_position(line);

	if (is_string(help_list))
		cshelp("features", help_list);
	else if (is_list(help_list) && length_of_list(help_list) >= line)
		cshelp("features", help_list[line - 1]);
	else
		cshelp("features", read());
}
/**********************************************************************/
/*   Registered  macro  --  called when user hits a key which causes  */
/*   a  self_insert.  If  its  upper/lower  case  letter  go to line  */
/*   which  has  this  at  the beginning. If its a <Ctrl-leter> then  */
/*   go to that entry and pretend user hit <Return>.		      */
/**********************************************************************/
void
sel_alpha()
{
	string ch, pat, real_ch;
	int	current_item;

	prev_char();

	real_ch = ch = read(1);
	if (atoi(ch, 0) < ' ')
		sprintf(ch, "%c", atoi(ch, 0) + '@');
	delete_char();
	inq_position(current_item);

	pat = "<[ ]@" + quote_regexp(ch);
	move_rel(0, 1);
	if (re_search(NULL, pat) || re_search(NULL, upper(pat))) {
		raise_anchor();
		drop_anchor(3);
		move_rel(0, -1);
		}
	else {
		top_of_buffer();
		if (re_search(NULL, pat) || re_search(NULL, upper(pat))) {
			raise_anchor();
			drop_anchor(3);
			move_rel(0, -1);
			}
		else
			move_abs(current_item, 0);
		}
	if (atoi(real_ch, 0) < ' ')
		push_back(key_to_int("<Enter>"));
}
/**********************************************************************/
/*   This  function  is  called when user selects an item in a list,  */
/*   and there is an action to perform.				      */
/**********************************************************************/
void
sel_list()
{	int	line;
	declare	function;
	extern list do_list;

	/***********************************************/
	/*   Find  the  name  of  the  macro  to call  */
	/*   based on the current line number.	       */
	/***********************************************/
	inq_position(line);
	-- line;
	function = do_list[line];
	/***********************************************/
	/*   We  only  support strings containing the  */
	/*   macro name at present.		       */
	/***********************************************/
	if (is_string(function)) {
		/***********************************************/
		/*   If  string  is  blank,  then  we have an  */
		/*   exit action.			       */
		/***********************************************/
		if (function == "")
			exit();
		else {
			execute_macro(function);
			}
		}
	else
		select_list("XYZ", "ABC", 1, function);
}
/**********************************************************************/
/*   This  function  called  by the mouse code to 'warp' the cursor.  */
/*   The  user  clicked on an entry and we want to unhilight the old  */
/*   entry and hilight the currrent.				      */
/**********************************************************************/
void
sel_warp()
{
	raise_anchor();
	drop_anchor(MK_LINE);
	beginning_of_line();
}
/***********************************************************************
/*   This macro returns a window buffer of a reasonable size for
/*   the terminal. This macro takes into account that we may not be
/*   running on a plain 80x25, and gives us as many lines as will
/*	  fit on the screen whilst taking the callers request into account.
/*
/*	(macro sized_window
/*		lines		maximum number of lines wanted.
/*		width		width of window.
/*		msg		message to appear on the bottom of the
/*		   		window.
/*		)
/***********************************************************************/
int
sized_window(int lines, int width, string msg)
{	int	curwin;
	int	newwin;
	int	screen_lines, screen_cols;
	int	lx, by, rx, ty;


	inq_screen_size(screen_lines, screen_cols);
	curwin = inq_window();

	if (width < 0)
		width = screen_cols - MARGIN;
		
	lx = screen_cols - (window_offset + width) - 1;
	if (lx < 1)
		lx = 1;

	rx = lx + width;
	-- screen_cols;
	if (rx > screen_cols)
		rx = screen_cols;
	ty = top_line;
	by = ty + lines;

	if (by >= screen_lines - 4)
		by = screen_lines - 5;
	create_window(lx, by, rx, ty, msg);
	newwin = inq_window();
	set_window(curwin);

	return newwin;
	
}
/***********************************************************************/
/*
/*   Macro to display a list of items and allow the user to toggle
/*   the values in the list. The valid values for each item are given
/*   in a list-of-lists. This is a primitive but useful forms-entry
/*   macro.
/*
/*   Usage:
/*         field_list("title to appear on top of window",
/*                     list of default values,
/*                     list of items and values
/*                     )
/*
/*   Returns:
/*         List of integers indicating which options were selected.
/***********************************************************************/
list
field_list(string title, list result, list arg)
{
	int	old_buf,
		old_win,
		buf,
		line = 0,
		ty;
	int	width;
	string	s, msg;
	declare	v;
																				   
	old_buf = inq_buffer();
	old_win = inq_window();
	
	buf = create_buffer(title, NULL, 1);
	set_buffer(buf);
	
	while (1) {
		if (!field_display(arg, line, FALSE))
			break;
		++ line;
		}
	++ line;
	
	ty = 4;
	msg = "<Up>, <Down> to select. <Enter> to exit.";
	width = 60;
	if (strlen(msg) > width)
		width = strlen(msg) + 4;
	if (inq_line_length() > width)
		width = inq_line_length() + 4;
	create_window(5, ty + line, 5 + width, ty, msg);
	attach_buffer(buf);
	
	line = 0;
	field_display(arg, line, TRUE);
	keyboard_push();
	
	keyboard_typeables();
	assign_to_key("<Backspace>", "field_bs");
	assign_to_key("<Esc>", "exit");
	assign_to_key("<Enter>", "exit");
	assign_to_key("<Down>", "field_down");
	assign_to_key("<Up>", "field_up");
	assign_to_key("<Space>", "field_space");
	process();
	
	keyboard_pop();
	message("");

	/***********************************************/
	/*   The  results  list  needs  to  have  the  */
	/*   actual  value  for  non-toggling fields,  */
	/*   rather  than  just  an  index  into  the  */
	/*   actual  value.  So, we scan the list and  */
	/*   put  in  the  actual value read from the  */
	/*   buffer.				       */
	/***********************************************/
	top_of_buffer();
	for (line = 0; line < inq_lines(); line++) {
		/***********************************************/
		/*   Check  to  see  if  we  have a string at  */
		/*   this  position.  We have to assign value  */
		/*   to  a  polymorphic  variable  so we test  */
		/*   its type.				       */
		/***********************************************/
		v = result[line];
		if (is_string(v)) {
			s = read();
			result[line] = ltrim(trim(substr(s, index(s, ":") + 2)));
			}
		down();
		}
	
	delete_window();
	delete_buffer(buf);
	set_window(old_win);
	set_buffer(old_buf);
	return result;
}
/**********************************************************************/
/*   Function  called  when user hits the <Backspace> key in a field  */
/*   list.							      */
/**********************************************************************/
void
field_bs()
{	int	col;
	int	colon_col;

	inq_position(NULL, col);
	beginning_of_line();
	colon_col = index(read(), ":") + 2;
	move_abs(NULL, col);
	if (col > colon_col)
		backspace();
}
void
field_down()
{	extern int line;
	extern list arg;
	
	++line;
	field_display(arg, line, TRUE);
}
void
field_up()
{	extern int line;
	extern list arg;

	if (line > 0) {
		-- line;
		field_display(arg, line, TRUE);
		}
}
void
field_space()
{	int	l, m;
	declare option;
	extern int line;
	extern list arg, result;

	l = line;
	option = arg[line*2 + 1];
	if (!is_list(option)) {
		self_insert(' ');
		return;
		}
	m = result[l];
	if (length_of_list(option) > m + 1)
		result[l] = m+1;
	else
		result[l] = 0;

	field_display(arg, l, TRUE);
}
int
field_display(list arg, int n, int msg_flag)
{
 	declare	m;
	declare prompt, option;
	extern list result;

	m = result[n];
	if (is_string(m))
		m = atoi(m);
	prompt = arg[n*2];
	if (!is_string(prompt))
		return FALSE;

	raise_anchor();

	move_abs(n+1, 1);
	delete_line();
	insert(prompt);
	
	save_position();
	option = arg[n*2 + 1];
	if (is_string(option)) {
		if (msg_flag)
			message("Type in new value of option.");
		insert(option);
		}
	if (is_list(option)) {
		if (msg_flag)
			message("Press <Space> to select alternate option.");
		insert(option[m]);
		}
	insert("\n");
	restore_position();
	drop_anchor(4);
	end_of_line();
	
	return TRUE;
}
