/*
**		BRIEF -- Basic Reconfigurable Interactive Editing Facility
**
**		Written by Dave Nanian and Michael Strickman.
*/

#include "dialog.h"
#include "win_ctrl.h"

/*
**		help.cb:
**
**		This macro contains the BRIEF help system.  Display of menus and the like
**	is handled by the BRIEF dialog manager.
*/

#define FIRST_MENU_LINE   3
#define FIRST_MENU_COL	  20

int	_user_kbd;
string	prev_func;		// name of the previous menu action function

int process_help_menu (string buf_name, string file_name, ~int height, ~int width);
int _help_add_button (~string buffer_name);
void to_top (void);
string search_path (string path, string name);
int display_help (string topic, ~string help_file);
void mouse_help(int event, int modifier, int parm2, int parm3);
void mouse_help_menu(int event, int modifier, int parm2, int parm3);
void _help_down();
void _help_up();
void _help_exit ();

/*
**		help:
**
**		This macro is used as the main help command.  It's called when
**	the user presses Alt-h.
*/

void help ()
{
	_user_kbd = inq_keyboard ();
	process_help_menu ("Help Menu", "help.mnu");
}

/*
**		_context_help:
**
**		This macro is used as the "help" registered macro.  It is called
**	when a prompt is displayed on the status line and the user presses
**	Alt-h.  Write a replacement macro if you want your own help to be
**	be available for your own commands.  The macro should check if the
**	last command is one of your macros, and if so call display_help
**	with the name of your help file; if not, just call _context_help
**	(so that all replacement macros are eventually called).
*/

void _context_help ()
{
	_user_kbd = inq_keyboard ();
	display_help (inq_command ());
}

/*
**		process_help_menu:
**
**		This macro takes four parameters:  the buffer name, file name,
**	height and width of the menu.  It sets things up and calls the
**	dialog manager to deal with the actual display and manipulation of
**	the menus.	(If height & width are omitted, menu is sized
**	automatically based on the contents of the menu buffer.)
*/

int process_help_menu (string buf_name, string file_name, ~int height, ~int width)
{
	int	startline,
			startcol;

	/*
	**		Successive menus are offset by a constant amount from the
	**	first one.	The dialog manager automatically keeps track of
	**	the current menu level.  Note that if height and width are
	**	omitted, height == startline and width == startcol.
	*/

	height += startline = FIRST_MENU_LINE + _dialog_level;
	width += startcol = FIRST_MENU_COL + _dialog_level * 2;

	/*
	**		Here we call the dialog manager to create the help menu.
	**	Whenever an interesting event occurs, the dialog manager will
	**	call process_help_event.
	**
	**		Note that process_help_event function may call this
	**	routine to display an additional menu.
	*/

	_process_menu (startcol, height, width, startline, buf_name, "Select a Category", file_name, NULL, "process_help_event", TRUE);

	returns (TRUE);
}

/*
**		process_help_event:
**
**		Action macro called by the dialog manager.  Used to add buttons
**	to the menu or handle picking from the menu.
*/

int process_help_event (int key, ...)
{
	string	action;

	/*
	**		If a menu button was picked, we parse the action off
	**	the menu button and execute it.
	*/

	if (key == DIALOG_INIT)
		{
		/*
		**	Setup a mouse event handler to allow button 2 click to 
		** exit the current menu and Ctrl+ button 2 click to exit help
		*/
		prev_func = inq_mouse_action();
		set_mouse_action( "mouse_help_menu" );
		}
	else if (key == DIALOG_PICK_MENU)
		{
		get_parm (2, action);
		execute_macro (substr (action, index (action, ";") + 1));
		}
	else if (key == DIALOG_CREATE_MENU)
		{
		get_parm (2, action);
		_help_add_button (action);
		}
	returns (TRUE);
}

/*
**		display_help:
**
**		display_help takes a help keyword and displays the appropriate
**	help information in an overlapping window.  It uses the global
**	variable _dialog_level to tie the grey - and Esc keystrokes in
**	with the rest of the menu package (grey - will remove the window,
**	and Esc will remove windows until the _dialog_level is 0).
**
**		The optional second parameter is the name of the help file.
*/

int display_help (string, ~string)
{
	int	displayed_help,
			orig_buffer,
			file_buffer;

	string	commands = "help.txt";

	get_parm (1, commands);

	if (file_buffer = create_buffer ("Help File", search_path (_dialog_dir, commands), 1))
		{
		string	topic;

		get_parm (0, topic);
		message ("Locating help text...");

		orig_buffer = set_buffer (file_buffer);
		tabs (3);

		if (search_fwd (("\xc" + topic) + "\xc") || search_back (("\xc" + topic) + "\xc"))
			{
			int	loc;

			message ("Help display for %s.", topic);

			if (loc = index (commands = read (), "\xc\""))
				commands = substr (commands, loc + 1, strlen (commands) - (loc + 2));
			else
				commands = "";

			beginning_of_line ();
			down ();

			/*
			** hide the zoom button and horizontial scroll bars for the help
			** information windows
			*/
			set_ctrl_state( ZOOM_BTN,    HIDE_CTRL );	// dont display a zoom button
			set_ctrl_state( HORZ_SCROLL, HIDE_CTRL );	// dont display a horizontial scroll bar
			
			if (inq_called () == "_context_help") {
				create_window (2, 21, 76, 2, "<Alt-h> for assignment, <Esc> or <Keypad-minus> to exit help");
			} else {
				create_window (2, 21, 76, 2, "<Keypad-minus> for menu, <Alt-h> for assignment, <Esc> to exit help");
			}

			attach_buffer (file_buffer);

			keyboard_push ();
			assign_to_key ("<Keypad-minus>", "_exit");
			assign_to_key ("<Esc>", "_help_exit");
			assign_to_key ("<PgUp>", "_help_up");
			assign_to_key ("<PgDn>", "_help_down");
			assign_to_key ("<Alt-h>", "_help_assignment " + commands);

			/* establish the mouse action handler for help */
			set_mouse_action( "mouse_help" );

			refresh ();

			process ();
			keyboard_pop ();
			delete_window ();
			search_back (("\xc" + topic) + "\xc");
			beginning_of_line ();
			displayed_help++;
			/*
			**	allow display of zoom button and horizontial scroll bar
			** for subsequent window creation
			*/
			set_ctrl_state( ZOOM_BTN,    SHOW_CTRL );	// display a zoom button
			set_ctrl_state( HORZ_SCROLL, SHOW_CTRL );	// display a horizontial scroll bar

			}
		else
			error ("No help available for %s.", topic);

		set_buffer (orig_buffer);
		}
	else
		error ("Help file not found.");

	returns (displayed_help);
}

/*
**		mouse_help
**
**		Handle all the mouse events of interest for help text windows
**
** Mouse assignments
** BTN1_CLICK 	- display key assignment i.e. <Alt-h>
** BTN2_CLICK  - to previous screen i.e. <Keypad-minus>
** BTN2_CLICK + CTRL  - exit help  i.e. <Esc>
** SET_WIN outside text window - close the window
**
*/
void mouse_help	(	int event, 			// the event code
							int modifier,	  	// modifier keys
							int parm2,			// line or scroll bar code
							int parm3 )			// column or thumb position
{

  	/* handle the events of interest */

	switch ( event ){
		case BTN1_CLICK:					// display key assignment
			execute_macro( inq_assignment( "<Alt-h>" ));

		case CLOSE_WIN:					// close the window
		case BTN2_CLICK:					// to previous screen
         switch ( modifier ){
            case 0:                 // to previous screen
      			exit();
            case KB_CTRL:           // exit the help system
      			_help_exit();

         }

      case SET_WIN:                 // click outside window is close window
         if ( parm2 != inq_window() ) exit();

		case VSCROLL:						// vertical scroll bar event
			switch ( parm2 ){
				case SB_BOTTOM:			// end of this help text
				{
					int old_line, line;
					inq_position (old_line);		// save current position
					search_fwd ("\xc");				// look for next topic in help file
					inq_position (line);				// get its position
					if (line - old_line <= 18) {
						/* current topic is only one "page", go to original position */
						move_abs (old_line);
					} else {
						/* current topic is more than one "page" go to last page */
						move_abs ( line - 18 );
					}
				}

				case SB_TOP:				// top of this help text
				{
					int old_line, line;
					inq_position (old_line);		// save current position
					search_back ("\xc");				// look for next topic in help file
					inq_position (line);				// get its position
					if (old_line - line <= 18) {
						/* current topic is only one "page", go to original position */
						move_abs (old_line);
					} else {
						/* current topic is more than one "page" go to first page */
						move_abs ( line+1 );
					}
				}

				case SB_LINEDOWN:
				case SB_PAGEDOWN:			// page down request
					_help_down();
				case SB_LINEUP:
				case SB_PAGEUP:			// page up request
					_help_up();
			}

	}
}


/*
**		_help_down:
**
**		_help_down is called when the PgDn key is pressed:  it ensures that
**	the user does not go past the end of the help file.  Since the command
**	name begins with an underscore, the current command is not reassigned.
*/

void _help_down ()
{
	int	line,
			col,
			old_line,
			old_col;

	inq_position (old_line, old_col);
	search_fwd ("\xc");
	inq_position (line, col);
	move_abs (old_line, old_col);

	page_down ();

	if (line - old_line <= 18)
		move_abs (old_line, old_col);
}

/*
**		_help_up:
**
**		Goes up a page in the help file.  Since the command name begins
**	with an underscore, the current command is not reassigned.
*/

void _help_up ()
{
	int	line,
			col,
			old_line,
			old_col;

	inq_position (old_line, old_col);
	search_back ("\xc");
	inq_position (line, col);
	move_abs (old_line, old_col);

	page_up ();

	if (old_line - line <= 18)
		move_abs (old_line, old_col);
}

/*
**		key_specific_help:
**
**		Gets help for the user using the keys, not the menus.
*/

void key_specific_help ()
{
	int	saved_level = _dialog_level,
			key_value;

	string	sequence,
				assignment;

	/*
	**		The dialog level is saved and restored when we enter and exit
	**	key-specific help.  During execution, we set the _dialog_level to
	**	0;  a press of Esc will then only remove the help screen.
	**
	**		If this wasn't done, extra "exit" calls would be done, and
	**	the next time a window came up it would be removed immediately.
	*/

	_dialog_level = 0;
	keyboard_push (_user_kbd);
	message ("Press the key you want help on, <Esc> to exit.");

	while (key_value != key_to_int ("<Esc>"))
		{
		while ((key_value = read_char ()) == -1);
		sprintf (sequence, "#%u", key_value);

		/*
		**		If the key assignment we have obtained is ambiguous, we keep
		** appending additional key assignments to it until we find a
		** command (or fail).
		*/

		while ((assignment = inq_assignment (sequence)) == "ambiguous")
			{
			message ("Press next key in sequence, <Esc> to exit.");

			while ((key_value = read_char ()) == -1);

			if (key_value == key_to_int ("<Esc>"))
				break;

			sprintf (sequence, "%s#%u", sequence, key_value);
			}
		if (key_value == key_to_int ("<Esc>"))
			break;

		if (display_help (assignment))
			message ("Press the key you want help on, <Esc> to exit.");
		}
	_dialog_level = saved_level;
	keyboard_pop (1);
	message ("");
}

/*
**		_help_assignment:
**
**		This macro takes a command name as a parameter, creates a window
**	and displays the keys assigned to that command.
*/

_help_assignment (string commands)
{
	string	this_command,
				keys;

	int	old_buf,
			key_buf,
			loc,
			comma_loc,
			semi_loc,
			key_read,
			have_assignment;

	keyboard_push (_user_kbd);

	old_buf = set_buffer (key_buf = create_buffer ("Keys", NULL, 1));
	tabs (4);

	while (strlen (commands))
		{
		if (semi_loc && (semi_loc < comma_loc || !comma_loc))
			insert ("\n\nSee also:\n");

		comma_loc = index (commands, ",");
		semi_loc = index (commands, ";");

		if (semi_loc && (semi_loc < comma_loc || !comma_loc))
			loc = semi_loc;
		else
			loc = comma_loc;

		if (loc)
			{
			this_command = substr (commands, 1, loc - 1);
			commands = substr (commands, loc + 1);
			}
		else
			{
			this_command = commands;
			commands = "";
			}
		if (this_command != "")
			{
			++have_assignment;

			if ((keys = inq_assignment (this_command, 1)) == "")
				keys = inq_assignment ("execute_macro", 1) + "<-and> " + this_command;

			insert ("\n \"" + this_command + "\" is assigned to:\n\t\t");

			while (loc = index (keys, "<-"))
				{
				insert (substr (keys, 1, loc - 1));
				keys = substr (keys, loc);

				switch (substr (keys, 1, index (keys, ">")))
					{
					case "<-and>":
						keys = substr (keys, 7);

					case "<-also>":
						{
						insert ("\n\t\t");
						keys = substr (keys, 8);
						}
					default:
						{
						keys = "";

						/*
						**		Note:  this breaks from the loop, not the switch.
						*/

						break;
						}
					}
				}
			insert (keys + "\n");
			}
		}

	/*
	**		If any assignments were found, we display them in a window.
	*/

	if (have_assignment)
		{
		keyboard_push ();
		assign_to_key ("<Esc>", "_exit");
		assign_to_key ("<Keypad-minus>", "_exit");
		assign_to_key ("<PgUp>", "_assign_up");
		assign_to_key ("<PgDn>", "_assign_down");
		top_of_buffer ();
		create_window (6, 15, 72, 4, "<Esc> or <Keypad-minus> to exit, <PgUp> or <PgDn> to scroll");
		attach_buffer (key_buf);
		/* establish the mouse action handler for this help window */
		set_mouse_action( "mouse_help" );

		refresh ();
		process ();
		keyboard_pop ();
		delete_window ();
		}
	if (!have_assignment)
		beep ();

	delete_buffer (key_buf);
	set_buffer (old_buf);
	keyboard_pop (1);
}

void _assign_up ()
{
	page_up ();
}

void _assign_down ()
{
	page_down ();
	move_rel (inq_position () * -1);
}

/*
**		_help_exit:
**
**		Exits from the help system; calls exit to remove the help
**	window, and pushes back an <Esc> if the dialog manager is active
**	to remove the other (menu) windows.
*/

void _help_exit ()
{
	if (_dialog_level)
		push_back (key_to_int ("<Esc>"));

	exit ();
}

/*
**		_help_add_button:
**
**		Used to add buttons to existing menus.  This macro does nothing,
**	but you can write a replacement macro for it that checks the only
**	parameter (the buffer name) and adds buttons.  When your replacement
**	macro is called, the menu buffer will be current and the menu will
**	be unformatted (just as it is on disk).
**		Make sure your replacement calls _help_add_button so that all
**	other replacement macros eventually get called.
*/

int _help_add_button (~string)
{
	returns (TRUE);
}

/*
**	mouse_help_menu
**
**	Sub-class the default menu handler for the help system
**	
**	Mouse action assignments:
**   default menu actions plus
** Click 2        - exit this menu
**	Dbl Click 2		- exit the help system
**																	
*/
void mouse_help_menu(int event, 			// the event code
							int modifier,	  	// modifier keys
							int parm2,			// line or scroll bar code
							int parm3 )			// column or thumb position
{
	/* handle the events of interest */
	switch ( event ){
		case BTN2_CLICK:					// to previous screen
         switch ( modifier ){
            case 0:                 // exit this menu
      			exit();
            case KB_CTRL:           // exit the help system
      			_help_exit();

         }

		default:
			/* let the previous event handler for menus process the event */
			execute_macro( prev_func, event, modifier, parm2, parm3 );

	}
}


