/*
**		BRIEF -- Basic Reconfigurable Interactive Editing Facility
**
**
**		dlg_menu.cb:
**
**		This file contains all BRIEF macros for managing menus provided
**    by the dialog manager.
*/

#include "dialog.h"
#include "dlg.h"
#include "win_ctrl.h"

/*
**		mouse_menu
**
**		Handle all the mouse events of interest for menus
**
** Mouse assignments
** BTN1 DOWN, UP, MOVE - highlight a menu entry
** BTN1_CLICK				- pick a menu choice
** BTN2_CLICK 				- close the menu
*/
void mouse_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 ){
		/*
		** Note: the double click is ignored because the CLICK message
		** comes thru before the double click message so the click causes
		** the menu selection.
		*/
		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
						_menu_highlight();		// highlight the menu choice
					}
					if ( event == BTN1_CLICK )	_dialog_menu_pick();
				}
			}
		}

		/*
		** Close the menu if the user clicks in the status area.
		** If you would like a click in the status area, when a
		** menu is up to mean shut down the menu and bring up
		** a command prompt, then remove the test for BTN1_CLICK
		** and just exit.  Then the BTN1_DOWN message will cause
		** the menu to exit and the BTN1_CLICK will pass thru to
		** the default handler which will bring up the command prompt
		*/
 		case STATUS_AREA:
			switch ( parm2 ) {
				case BTN1_CLICK:
					exit();
			}

		/* close the menu if the close button clicked on this menu */
		case CLOSE_WIN:
			if ( inq_window() == parm2 ) exit(); // close this menu

		/* close the menu if the user clicked outside of it */
		case SET_WIN:
			if ( inq_window() != parm2 ) exit(); // close this menu

		case VSCROLL:						// vertical scroll bar event
			switch ( parm2 ){
				case SB_BOTTOM:			// end of menu request
					_dialog_menu_end();
				case SB_TOP:				// top of menu request
					_dialog_menu_home();
				case SB_PAGEDOWN:			// page down request
					_dialog_menu_pgdn();
				case SB_PAGEUP:			// page up request
					_dialog_menu_pgup();
				case SB_LINEDOWN:			// line down request
					_dialog_menu_down();
				case SB_LINEUP:			// line up request
					_dialog_menu_up();
				case SB_THUMBTRACK:{		// scroll while tracking the thumb
					raise_anchor();				// erase current highlight

					/* set the thumb position */

					set_ctrl_state(VERT_THUMB, parm3);

					/* make sure we're at the left col */

					move_abs(0, 1);

					_menu_highlight();			// highlight the menu choice
				}
			}

	}
}

/*
**		_menu_highlight:
**
**		Calls the menu alter event.  If it succeeds, highlights the
**	current menu line using a line mark.
*/

int _menu_highlight ()
{
	int	line;

	inq_position (line);

	if (execute_macro (_dialog_action_func, DIALOG_MOVE_MENU, line, _menu_button ()))
		{
		raise_anchor ();
		drop_anchor (3);
		returns (TRUE);
		}
	else
		returns (FALSE);
}

/*
**		_dialog_menu_home:
**
**		Moves the selection to the first selectable item on the menu.
**	Calls the action function with the new line number and menu button
**	text.
*/

void _dialog_menu_home ()
{
	top_of_buffer ();

	while (!_menu_highlight ())
		down ();
}

/*
**		_dialog_menu_end:
**
**		Moves to the last selectable item on the menu.	Calls the
**	action function with the new line number and menu button text.
*/

void _dialog_menu_end ()
{
	end_of_buffer ();
	beginning_of_line ();
	to_bottom ();

	while (!_menu_highlight ())
		up ();
}

/*
**		_dialog_menu_pgup, _dialog_menu_pgdn:
**
**		Pages up or down in a menu.
*/

void _dialog_menu_pgup ()
{
	page_up ();

	if (!(_menu_highlight () || _dialog_menu_up ()))
		_dialog_menu_down ();
}

void _dialog_menu_pgdn ()
{
	page_down ();

	while (inq_position ())
		up ();

	if (!(_menu_highlight () || _dialog_menu_down ()))
		_dialog_menu_up ();
}

/*
**		_dialog_menu_down:
**
**		Moves the current selection down, stopping at next selectable
**	item; if the bottom of the menu is encountered, returns to the
**	previous position.  Returns whether or not it succeeded in moving.
**		Calls the action function with the new line number and menu
**	button text as parameters.
*/

int _dialog_menu_down ()
{
	int	failed;

	save_position ();

	while (TRUE)
		{
		down ();

		if (inq_position ())
			{
			failed++;
			break;
			}
		else if (_menu_highlight ())
			break;
		}
	restore_position (failed);
	returns (!failed);
}

/*
**		_dialog_menu_up:
**
**		Moves the current selection up to the previous selectable
**	item, if there are any.  Returns TRUE if it succeeded.
*/

int _dialog_menu_up ()
{
	int	moved;

	save_position ();

	while (up ())
		if (_menu_highlight ())
			{
			moved++;
			break;
			}
	if (!moved)
		set_top_left (1, 1);

	restore_position (!moved);
	returns (moved);
}

/*
**		_menu_go:
**
**		Reads a character (that was just inserted; _menu_go is
**	called by a type 0 registered macro) from the buffer and deletes
**	it, then looks for a matching button before the end of the menu.
**	If there is none, looks for one starting with the first item.
**	If there is still none, stays put.	(If a selectable button is found,
**	_menu_go moves to it and highlights it.)
*/

void _menu_go (int key)
{
	string	pattern;

	int	end_line,
			after_line,
			start_line,
			start_col,
			failed;

	/*
	**		Figure out the time when the key was pressed.  This is a
	**	relative number, measured in tenths of a second, and it is
	**	used to figure out when a user is typing a multi-key
	**	sequence.
	*/

	time (NULL, start_line, start_col, end_line);
	end_line = (start_line * 60 + start_col) * 10 + end_line / 10;
	after_line = end_line - _dialog_menu_time;
	_dialog_menu_time = end_line;

	while (after_line < 0)
		after_line += 32768;

	/*
	**		If .8 seconds or more elapsed since we last pressed a key in
	**	this menu, we clear the remembered prefix.
	*/

	if (after_line >= 8)
		_dialog_menu_prefix = "";

	/*
	**		Figure out what key was pressed.
	*/

	sprintf (pattern, "%c", key);

	/*
	**		If the character is a regular expression character, we make
	**	sure to put a backslash in front of it.
	*/

	if (index ("?*@+|\\<>%${}[]~-", pattern))
		pattern = "\\" + pattern;

	pattern = "<[ \\t]@" + (_dialog_menu_prefix += pattern);

	/*
	**		Find the last line of the window; if the search takes us
	**	outside it, we want to center the line in the window.
	*/

	inq_position (start_line, start_col);
	end_of_window ();
	inq_position (end_line);
	move_abs (start_line, start_col);

	/*
	**		If we are searching for a one-character button prefix, we
	**	search the current line last, not first.
	*/

	if (strlen (_dialog_menu_prefix) == 1)
		down ();

	/*
	**		Search from the current position to EOF, breaking if
	**	a selectable line is found.  If the pattern is not found,
	**	start searching from the top of the file down.
	*/

	while (!failed)
		{
		if (search_fwd (pattern, TRUE, FALSE) <= 0)
			{
			move_abs (start_line, start_col);
			drop_anchor (3);
			top_of_buffer ();

			while (!failed)
				{
				if (search_fwd (pattern, TRUE, FALSE, TRUE) <= 0)
					++failed;
				else if (_menu_highlight ())
					{
					inq_position (after_line);

					if (after_line > end_line)
						center_line ();

					return;
					}
				else
					down ();
				}
			/*
			**		If we can't find the current multi-character prefix in the
			**	menu, but the last two characters were the same, we throw
			**	away the second of those and look for the next prefix that
			**	matches.
			*/

			if ((failed = strlen (_dialog_menu_prefix)) > 1 && substr (_dialog_menu_prefix, failed, 1) == substr (_dialog_menu_prefix, failed - 1, 1))
				{
				_dialog_menu_prefix = substr (_dialog_menu_prefix, 1, failed - 1);
				pattern = "<[ \\t]@" + _dialog_menu_prefix;
				move_abs (start_line, start_col);
				down ();
				failed = FALSE;
				}
			else
				failed = TRUE;

			raise_anchor ();
			}
		else if (_menu_highlight ())
			{
			inq_position (after_line);

			if (after_line > end_line)
				center_line ();

			return;
			}
		else
			down ();
		}
	move_abs (start_line, start_col);
}

/*
**		_dialog_menu_pick:
**
**		Processes the menu line that is selected when the user presses
**	Enter.
*/

void _dialog_menu_pick ()
{
	int	line;

	inq_position (line);

	if (execute_macro (_dialog_action_func, DIALOG_PICK_MENU, line, _menu_button ()))
		_dialog_picked = line;
}

/*
**		_menu_button:
**
**		Returns the text of the current menu button, with leading and
**	trailing white space removed.
*/

string _menu_button ()
{
	returns (trim (ltrim (read ())));
}
