
/*
**		BRIEF -- Basic Reconfigurable Interactive Editing Facility
**
**		Written by Dave Nanian and Michael Strickman.
*/

/*
**		keys.cb:
**
**		This macro allows basic reconfiguration of the key assignments
**	for the commands provided with BRIEF.	It displays a menu containing
**	a list of commands, and an adjacent window containing the key
**	assignment(s) for the currently highlighted command.	By selecting
**	a command, the user can move into the window, and add or delete
**	key assignments with Ins and Del.
**
**		The macro uses the Dialog Manager for the menu it displays.  The
**	key assignments are changed by modifying and/or adding assign_to_key
**	calls to keyboard.h.  If no keyboard.h can be found, the macro can
**	be used as an online quick reference card, to find the key sequence
**	associated with each command.  If a non-zero parameter is supplied,
**	the macro inserts the key assignments into the current buffer, instead
**	of keyboard.h.
*/

#include "dialog.h"
#include "win_ctrl.h"

int	menu_buf,
		key_buf,
		include_buf,
		menu_win,
		key_win,
		orig_keyboard,
		key_parm;

string	search_text,
			bpath,
			include_file,
			button_text,
			assign_text;
string 	default_mouse_handler;			// default left side menu handler for mouse.

string add_to_path (string path, string file);
void _keys_message ();
void _keys_menu_message ();
string _get_command_str ();
string search_path (string pathvar, string filename);
void mouse_keys_left();
void mouse_keys_right();
void _dialog_menu_pick();
void mouse_menu(int, int, int, int);

/*
**		keys:
**
**		This macro sets up the buffers needed: one for the command menu,
**	one for the key assignment display, and one for keyboard.h.  It then
**	calls the Dialog Manager to process commands, and when the user
**	finishes, it writes keyboard.h and registers a macro to recompile
**	startup, if necessary.
*/

void keys ()
{
	int	old_buffer = inq_buffer ();

	string	message_str;

	orig_keyboard = inq_keyboard ();

	include_file = add_to_path (_dialog_dir, "commands.mnu");

	if (!exist (include_file))
		{
		error ("Unable to locate help files (commands.mnu).");
		return;
		}
	menu_buf = create_buffer ("Commands", include_file, 1);
	set_buffer (menu_buf);
	search_text = read ();

	bpath = inq_environment ("BPATH");

	if (substr (bpath, 1, 1) == ";")
		bpath = substr (bpath, 2);

	if (substr (bpath, 1, 1) == ".")
		bpath = substr (bpath, index (bpath, ";") + 1);

	if (bpath == "")
		bpath = "/brief/macros";

	include_file = search_path (bpath, "keyboard.h");

	key_parm = 0;
	get_parm (0, key_parm);

	if (!key_parm)
		include_buf = include_file == "" ? 0 : create_buffer ("Macro", include_file, 1);
	else
		{
		key_parm = old_buffer;
		include_buf = 0;
		}
	key_buf = create_buffer ("Assignments", NULL, 1);
	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( CLOSE_BTN, HIDE_CTRL);		// hide the close button for this window
	create_window (41, 21, 72, 1, include_buf || key_parm ? ", Del to delete, Ins to add" : "Current Key Assignments");
	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( CLOSE_BTN, SHOW_CTRL);		// show the close button for this window
	key_win = inq_window ();
	attach_buffer (key_buf);
	set_buffer (key_buf);
	refresh ();

	if (include_buf || key_parm)
		sprintf (message_str, ", , %c, Enter, Ins, or Esc", 26);
	else
		message_str = ", , Enter, or Esc";

	_process_menu (7, 1, 38, 1, NULL, message_str, NULL, menu_buf, "_keys_menu", 1);

	set_window (key_win);
	delete_window ();

	if (include_buf)
		{
		set_buffer (include_buf);

		if (inq_modified ())
			{
			if (get_parm (NULL, message_str, "Save changes permanently [yn]? ", 1) && index ("nN", message_str))
				message ("Changes only effective for this editing session.");
			else
				{
				message ("");			//	Clear cancelled message if there.
				register_macro (5, "_keys_exit");
				write_buffer ();
				message ("Changes saved.");
				}
			}
		if (old_buffer != include_buf)
			delete_buffer (include_buf);
		}
	set_buffer (old_buffer);
	delete_buffer (menu_buf);
	delete_buffer (key_buf);
	call_registered_macro (1);
}

/*
**		_keys_menu:
**
**		This macro is the action routine for the command menu.  It handles
**	initialization, changing the menu selection, choosing a menu item,
**	and terminating the menu.	All other menu events are irrelevant.
*/

int _keys_menu (int event_type, ~, string)
{
	int	text_found,
			old_keyboard;

	switch (event_type)
		{
		case DIALOG_INIT:
			{
			menu_win = inq_window ();
			get_parm (2, button_text);
	
			_keys_menu_message ();
			button_text = substr (button_text, index (button_text, ";") + 1);
			sprintf (search_text, "assign_to_key (\"%%s\", \"%s\"", button_text);

			set_buffer (key_buf);
			old_keyboard = inq_keyboard ();
			keyboard_push (orig_keyboard);
			assign_text = inq_assignment (button_text, 1);

			if (assign_text == "")
				assign_text = _get_command_str () + button_text;

			while (!inq_position ())
				delete_line ();

			insert (assign_text);
			top_of_buffer ();
			translate ("\\<-also\\>", "\n", 1);
			translate ("{\\<-and\\>}|{\\<-or\\>\\<*\\>}", "", 1);
			set_buffer (menu_buf);
			keyboard_pop (1);
         
			assign_to_key ("<Right>", "_dialog_menu_pick");

			if (include_buf || key_parm)
				{
				assign_to_key ("<Ins>", "_keys_insert");

				if (key_parm)
					assign_to_key ("<Enter>", "_keys_insert");
				}
			if (include_buf) 
				message ("Select item with Enter, add assignment with Ins.");
			else 
				message (" and  to view key assignments.");

			/* sub-class the mouse event handler for the menu */
			default_mouse_handler = inq_mouse_action();		// save current one
			set_mouse_action( "mouse_keys_left" );				// establich new one
		}

		case DIALOG_ALTER_MENU:	{

			_keys_menu_message ();
			get_parm (2, button_text);
			button_text = substr (button_text, index (button_text, ";") + 1);
			sprintf (search_text, "assign_to_key (\"%%s\", \"%s\"", button_text);

			set_buffer (key_buf);
			old_keyboard = inq_keyboard ();
			keyboard_push (orig_keyboard);
			assign_text = inq_assignment (button_text, 1);

			if (assign_text == "")
				assign_text = _get_command_str () + button_text;

			while (!inq_position ())
				delete_line ();

			insert (assign_text);
			top_of_buffer ();
			translate ("\\<-also\\>", "\n", 1);
			translate ("{\\<-and\\>}|{\\<-or\\>\\<*\\>}", "", 1);
			set_buffer (menu_buf);
			keyboard_pop (1);
		}

		case DIALOG_PICK_MENU: {

			if (!include_buf)
				{
				error ("Unable to find macro source; can't reconfigure.");
				return (1);
				}
			set_window (key_win);
			keyboard_push ();

			if (strlen (ltrim (read ())))
				drop_anchor (3);

			_keys_message ();
			assign_to_key ("<Up>", "_keys_up");
			assign_to_key ("<Down>", "_keys_down");
			assign_to_key ("<Ins>", "_keys_insert");
			assign_to_key ("<Del>", "_keys_delete");
			assign_to_key ("<Alt-d>", "_keys_delete");
			assign_to_key ("<Esc>", "exit");
			assign_to_key ("<F10>", "exit");
			assign_to_key ("<Left>", "exit");
			refresh ();

			/* specify the mouse event handler for the right-hand window */
	 		set_mouse_action( "mouse_keys_right" );	

			process ();
			keyboard_pop ();

			/*		If the window has not yet been reset, need to raise the
			**	anchor
			*/

			if (inq_window() != menu_win)
				raise_anchor ();

			set_window (menu_win);
			_keys_menu_message ();
		}
		case DIALOG_TERM: {
			message ("Returning to editing session...");
			assign_to_key ("<Ins>", "nothing");
			assign_to_key ("<Right>", "nothing");
			assign_to_key ("<Enter>", "_dialog_menu_pick");
			}
		}
	return (1);
}
																		 
/*
**		_keys_up:
**
**		When the user presses the up arrow in the Assignments window,
**	this macro is called.  If the user isn't at the top of the buffer,
**	it moves the mark up one line.
*/

void _keys_up ()
{
	int	line;

	inq_position (line);

	if (line > 1)
		{
		move_rel (-1, 0);
		raise_anchor ();
		drop_anchor (3);
		}
}

/*
**		_keys_down:
**
**		Likewise, when the user presses the down arrow in the Assignments
**	window, this macro is called.  If the user isn't at the bottom of the
**	buffer, it moves the mark down one line.
*/

void _keys_down ()
{
	move_rel (1, 0);

	if (!inq_position ())
		{
		raise_anchor ();
		drop_anchor (3);
		}
	else
		move_rel (-1, 0);
}

/*
**		_keys_insert:
**
**		This one does all the work.  If the user wants to add a key
**	assignment, it asks for the new assignment, gets rid of the old
**	assignment (both in the Assignment window and in the keyboard.h
**	file), and inserts the new assign_to_key statement at the bottom
**	of the first macro in keyboard.h (the keyboard macro), or in the
**	current buffer, depending on the parameter passed to keys.
*/

void _keys_insert ()
{
	int	key_value,
			old_win = inq_window (),
			old_buffer,
			found,
			i,
			abort_insert;

	string	key_string,
				insert_string,
				cur_key;

	old_buffer = inq_buffer ();
	set_window (key_win);

	message ("Type the key sequence; press Enter to end.");
	while ((key_value = read_char ()) == -1);
	insert ("\n");
	up ();

	while (key_value != key_to_int ("<Enter>"))
		{
		cur_key = int_to_key (key_value);
		error ("cur_key is %s", cur_key);
		insert (cur_key);
		key_string += cur_key;
		message ("Continue; press Enter to end, Esc to cancel.", key_string);

		if (++i == 5)
			break;

		while ((key_value = read_char ()) == -1);

		if (key_value == key_to_int ("<Esc>"))
			{
			abort_insert = 1;
			break;
			}
		}
	delete_line ();
	beginning_of_line ();

	if (abort_insert)
		message ("Keystroke cancelled.");
	else
		{
		if (key_string == "")
			key_string = "<Enter>";

		message ("Processing assignment...");
		keyboard_push (orig_keyboard);
		assign_to_key (key_string, button_text);
		keyboard_pop (1);

		save_position ();
		top_of_buffer ();
		found = search_fwd (key_string, 0, NULL, 0);
		restore_position ();
		set_buffer (old_buffer);

		if (found)
			error ("Key sequence already assigned.");
		else
			{
			sprintf (insert_string, "assign_to_key (\"%s\",", key_string);

			if (include_buf)
				{
				set_buffer (include_buf);
				top_of_buffer ();

				if (search_fwd (insert_string, 0))
					{
					delete_line ();
					beginning_of_line ();
					}
				}
			insert_string += " \"";
			insert_string += button_text;

			if (include_buf)
				{
				if (search_fwd ("<\\c\\}"))
					{
					insert ("\t\t" + insert_string + "\");\n");
					up ();
					sprintf (insert_string, "Assigned %s to %%s.", button_text);
					message (insert_string, key_string);
					}
				else
					error ("Unable to locate end of macro.");
				}
			else
				{
				if (key_parm)
					{
					set_buffer (key_parm);
					insert (insert_string + "\");\n");
					}
				message ("String inserted in current buffer.");
				}
			set_buffer (key_buf);
			insert (key_string + "\n");
			raise_anchor ();

			if (read () == "\n")
				delete_line ();

			up ();
			drop_anchor (3);
			}
		}
	set_buffer (old_buffer);
	set_window (old_win);
}

/*
**		_keys_delete:
**
**		This macro is called when the user deletes a key assignment.
**	It assigns the key sequence to "nothing" in the original keyboard,
**	and deletes the text from the Assignments window and the keyboard.h
**	file (if it is there).
*/

void _keys_delete ()
{
	string	key_string,
				search_str,
				command_str;

	key_string = trim (read ());
	command_str = _get_command_str ();

	if (substr (key_string, 1, strlen (command_str)) != command_str)
		{
		keyboard_push (orig_keyboard);

		assign_to_key (key_string, "nothing");
		set_buffer (include_buf);
		message ("Locating old assignment...");
		top_of_buffer ();
		sprintf (search_str, search_text, key_string);

		if (search_fwd (search_str, 0, 0))
			{
			delete_line ();
			beginning_of_line ();
			}
		else if (search_fwd ("<\\c[ \t]+)\n)"))
			{
			sprintf (search_str, "\t\tassign_to_key (\"%s\", \"nothing\");\n", key_string);
			insert (search_str);
			up ();
			}
		else
			error ("Unable to locate end of macro.");

		set_buffer (key_buf);
		set_window (key_win);
		delete_block ();

		if (inq_position ())
			up ();

		if (strlen (ltrim (read ())))
			drop_anchor (3);

		keyboard_pop (1);
		_keys_message ();
		}
}

/*
**		_keys_exit:
**
**		This macro is called when the user exits, if there have been
**	any modifications to keyboard.h.  It recompiles the startup macro,
**	so the assignments will be in effect next time BRIEF is run.
*/

void _keys_exit (...)
{
	int	old_buffer = inq_buffer ();

	string	command_string;

	if (include_buf = create_buffer ("Macros", include_file, 1))
		{
		set_buffer (include_buf);
		message ("Recompiling startup macro...");

		if (inq_modified ())
			write_buffer ();

		sprintf (command_string, "cb %s >&temp.$b$", search_path (bpath, "startup.cb"));

		if (dos (command_string))
			{
			int	old_pause_on_error = pause_on_error (1);

			error ("Compile failed; run \"cb startup\". Press a key.");
			pause_on_error (old_pause_on_error);
			}
		else
			message ("Reconfiguration complete.");

		del ("temp.$b$");
		}
	else
		error ("Unable to locate %s.", include_file);

	set_buffer (old_buffer);
}

/*
**		_keys_message, _keys_menu_message:
**
**		These macros simply display the appropriate message when
**	they are called.
*/

void _keys_message ()
{
	message ("Press Ins to add assignment, Del to delete.");
}

void _keys_menu_message ()
{
	if (include_buf)
		message ("Select item with Enter, add assignment with Ins.");
	else
		message (" and  to view key assignments.");
}

/*
**		_get_command_str:
**
**		Gets the key sequence that is used for the execute_macro
**	primitive.	If multiple assignments exist, only the first one
**	is used.
*/

string _get_command_str ()
{
	string	command_str;

	int	command_len;

	keyboard_push (orig_keyboard);
	command_str = inq_assignment ("execute_macro", 1);
	keyboard_pop (1);

	if (command_len = index (command_str, "<-and>"))
		command_str = substr (command_str, 1, command_len - 1);

	if (command_len = index (command_str, "<-also>"))
		command_str = substr (command_str, 1, command_len - 1);

	returns (command_str + " ");
}

/*
**	mouse_keys_right
**
**	Handle all the mouse events of interest for the right-hand
**	window of the keys macro
**
**	Mouse action assignments:
**   default menu assignments plus
**	Click 2		- insert new entry		- like paste
**	Ctrl+ Click 2 - delete entry				- like cut
** If the user clicks in the other left-hand menu switch processing
** to that menu. Click outside any window exits.
**
**		int event, 		   the event code
**		int modifier	  	modifier keys
**		int parm2		   line or scroll bar code
**		int parm3 		   column or thumb position
**
*/

void mouse_keys_right(int event, int modifier, int parm2, int parm3 )			
{
	/* handle the events of interest */

	switch ( event ){
		case BTN1_DOWN:				// highlite a menu entry
		case BTN1_UP:					// highlite a menu entry
		case BTN1_MOVE: 				// highlite a menu entry
		case BTN1_CLICK: 				// pick a menu choice
			{
			int lines;

			if ( modifier == 0 )		// make sure no modifiers present
				{
				/* determine the number of lines in the buffer */

				save_position();

				end_of_buffer();		// position to the end of the buffer

				inq_position( lines );

				restore_position();

				if ( 0 < parm2 && parm2 <= lines )
					{
					inq_position(lines);

					if (lines != parm2) 		// did the mouse move to a new entry?
						{
						raise_anchor();			// erase current highlight
						move_abs( parm2, 0 );	// position to choice that was clicked on
						drop_anchor(3);			// highlight the menu choice
						}
					}
				}
			}

		case BTN2_CLICK:				// insert or delete a key assignment

			switch ( modifier ) 
				{
				case KB_CTRL:
					_keys_delete();

				case 0:
					_keys_insert();
				}

		case SET_WIN:						// the user clicked in another window
			/* was the left-hand menu selected?? */
			if ( parm2 == menu_win ) 
				{
				/*
				**	YES - exit this mouse event handler and let the menu event handler
				** for the left menu take over.  Before exiting this process loop
				** via the exit(), set the new current window to the left
				** menu to allow the mouse events for the window to come thru.
				** Specifically the BTN1_DOWN & BTN1_UP that follow the SET_WIN
				** event.
				*/

				raise_anchor ();

				set_window( parm2 );

				/*
				** Switch the action handler to the left menu because the
				** button down event will come thru here before Brief switches
				** out of this process loop.  We will figure out some way  
				** so this does not need to be done in the future.
				*/
				set_mouse_action( "mouse_keys_left" );
 				exit();
				}
			else 
				{
				/* a click outside both windows says to exit keys */

				exit();

				/* send the event to the left menu handler */

				set_window( menu_win );
 				execute_macro( "mouse_keys_left", event, modifier, parm2, parm3 );
				}

		/*
		**	Since there is no close button on the right window, see if the
		**	user clicked on the close button of the left window.  If so
		**	exit keys.
		*/

		case CLOSE_WIN:
			if ( parm2 == menu_win ) 
				{
				exit();
				/* send the event to the left menu handler */

				set_window( menu_win );

				execute_macro( "mouse_keys_left", event, modifier, parm2, parm3 );
				}

		}
}


/*
**	mouse_keys_left
**
**	Sub-class the default menu handler for the left menu
**	
**		int event, 		   the event code
**		int modifier	  	modifier keys
**		int parm2		   line or scroll bar code
**		int parm3 		   column or thumb position
**																	
*/

void mouse_keys_left(int event, int modifier, int parm2,	int parm3)			
{

	/* handle the events of interest */


	switch ( event )
		{
		case SET_WIN:						// if the user clicked in the right menu

			/*		Jam an Enter to bring us to the right window
			*/

			if ( parm2 == key_win ) 
				push_back(key_to_int("<Enter>"));		

			/* let the previous event handler for menus process the event 
			*/

			else 
				execute_macro( default_mouse_handler, event, modifier, parm2, parm3 );

		default:

			/* let the previous event handler for menus process the event 
			*/

			execute_macro( default_mouse_handler, event, modifier, parm2, parm3 );

	}
}
