/*
    ####             #    #     # #
    #   #            #    #       #          The FreeWare C library for 
    #   #  ##   ###  #  # #     # ###             RISC OS machines
    #   # #  # #     # #  #     # #  #   ___________________________________
    #   # ####  ###  ##   #     # #  #                                      
    #   # #        # # #  #     # #  #    Please refer to the accompanying
    ####   ### ####  #  # ##### # ###    documentation for conditions of use
    ________________________________________________________________________

    File:    Menu2.Menu2.c
    Author:  Copyright  1995 Julian Smith
    Version: 1.02 (11 Jan 1996)
    Purpose: Easy menu handling
    History: 1.00 (21 Jun 1995)
             1.01 (14 Nov 1995) Added SDLS-compatibility things like 
                                Desk_SDLS_PtrFn etc.
                                Fixed a bug which called the wrong selection
                                function if a non-leaf menuitem was chosen.
             1.02 (11 Jan 1996) Fixed bug which caused problems if 
                                Desk_message_MENUSDELETED wasn't received - the
                                old event handler wasn't released.
             1.10 (29 Aug 1996)	Added closefn.
*/


#include <stdlib.h>

#include "swis.h"

#include "Desk.Icon.h"
#include "Desk.WimpSWIs.h"
#include "Desk.Event.h"
#include "Desk.Menu.h"
#include "Desk.Error.h"
#include "Desk.Debug.h"
#include "Desk.Menu2.h"
#include "Desk.DeskMem.h"
#include "Desk.Window.h"


typedef struct	{
	const char		*title;
	const char		*spec;
	Desk_menu2_makefn	makefn;
	Desk_menu2_flagsfn	flagsfn;
	Desk_menu2_subfn	subfn;
	Desk_menu2_selectfn	selectfn;
	Desk_menu2_closefn	closefn;
	Desk_menu2_helpfn	helpfn;
	Desk_menu_ptr		menu;
	void			*reference;
	}
	Desk_menu2_block;



#define Desk_menu2_MAXNEST	10
static Desk_menu2_block		*Desk_menu2__openmenus[ Desk_menu2_MAXNEST+1];
static int			Desk_menu2__numopen = 0;



#ifdef Desk_DeskLib_DEBUG
	int	Desk_menu2_debuglevel = 0;
#endif



static void	Desk_Menu2__ClaimRelease( Desk_event_claimorreleasefn fn, void *reference);

#define Desk_Menu2__Claim( reference)			\
	Desk_Menu2__ClaimRelease( Desk_Event_Claim, reference)

#define Desk_Menu2__Release( reference)			\
	Desk_Menu2__ClaimRelease( Desk_Event_Release, reference)








static void	Desk_Menu2__FreeMenu( Desk_menu2_block *m)
/* Frees all data associated with Desk_menu2_block m	*/
/* Doesn't free submenus...			*/
	{
	Desk_menu_item	*item = Desk_Menu_FirstItem( m->menu);
	for( ; ; item++)	{
		Desk_Icon_DisposeIndData( &item->icondata, item->iconflags);
		if ( item->menuflags.data.last)	break;
		}
	Desk_DeskMem_Free( m->menu);
	m->menu = NULL;
	}


static void	Desk_Menu2__Free( int i)
	/* Free menus starting from nesting i	*/
	{
	int	ii;
	Desk_Debug2_Printf( "Desk_Menu2__Free, i=%i, Desk_menu2__numopen=%i\n", i, Desk_menu2__numopen);
	
	for ( ii=i; ii<Desk_menu2__numopen; ii++)	{
		
		Desk_Debug2_Printf( Desk_error_PLACE "Desk_menu2__openmenus[ ii(=%i)]->makefn = %p\n", 
			ii, Desk_menu2__openmenus[ ii]->makefn
			);
		
		if ( Desk_menu2__openmenus[ ii]->closefn)	{
			Desk_menu2__openmenus[ ii]->closefn( Desk_menu2__openmenus[ ii]->menu, Desk_menu2__openmenus[ ii]->reference);
			}
		
		if ( !Desk_menu2__openmenus[ ii]->makefn)	{
			Desk_Debug2_Printf( Desk_error_PLACE "Freeing menu number %i\n", ii);
			Desk_Menu2__FreeMenu( Desk_menu2__openmenus[ ii]);
			}
		else	{
			Desk_Debug2_Printf( "Haven't free-d menu - it had a special creator function\n");
			}
		/* We don't free menus we haven't created	*/
		}
	
	Desk_menu2__numopen = i;
	}


Desk_menu2_handle	Desk_Menu2_Create2( 
	const char		*title,
	const char		*spec,
	Desk_menu2_makefn	makefn, 	/* If !=NULL, called to make the menu	*/
	Desk_menu2_flagsfn	initfn, 	/* Called every time menu is opened	*/
	Desk_menu2_subfn	subfn, 		/* Called when submenu is needed	*/
	Desk_menu2_selectfn	selectfn, 	/* Called when selection is made	*/
	Desk_menu2_closefn	closefn,	/* Called when menu is closed		*/
	Desk_menu2_helpfn	helpfn,		/* Called when help is required		*/
	void			*reference
	)
	{
	Desk_menu2_block	*m = (Desk_menu2_block *) Desk_DeskMem_Malloc( sizeof( Desk_menu2_block));
		
	m->title	= title;
	m->spec		= spec;
	m->makefn	= makefn;
	m->flagsfn	= initfn;
	m->subfn	= subfn;
	m->selectfn	= selectfn;
	m->closefn	= closefn;
	m->helpfn	= helpfn;
	m->reference	= reference;
	m->menu		= NULL;
	
	Desk_Debug2_Printf( "Desk_Menu2_Create, title=%s, subfn=%p\n", title, subfn);
	
	return (Desk_menu2_handle) m;
	}

Desk_menu2_handle	Desk_Menu2_Create( 
	const char		*title,
	const char		*spec,
	Desk_menu2_makefn	makefn, 	/* If !=NULL, called to make the menu	*/
	Desk_menu2_flagsfn	initfn, 	/* Called every time menu is opened	*/
	Desk_menu2_subfn	subfn, 		/* Called when submenu is needed	*/
	Desk_menu2_selectfn	selectfn, 	/* Called when selection is made	*/
	Desk_menu2_closefn	closefn,	/* Called when menu is closed		*/
	void			*reference
	)
	{
	return Desk_Menu2_Create2( title, spec, makefn, initfn, subfn, selectfn, closefn, NULL, reference);
	}




static void	Desk_Menu2__EnsureMenu( Desk_menu2_block *m)
	{
	if ( m->makefn)	{
		Desk_Debug2_Printf( "Desk_Menu2__EnsureMenu calling makefn at %p\n", m->makefn);
		m->menu = m->makefn( m->reference);
		}
	else	{
		if ( !m->menu)	m->menu = Desk_Menu_New( m->title, m->spec);
		}
	
	if ( m->flagsfn)	{
		Desk_Debug2_Printf( "Desk_Menu2__EnsureMenu calling flagsfn at %p\n", m->flagsfn);
		m->flagsfn( m->menu, m->reference);
		}
	}




void	Desk_Menu2_Open( Desk_menu2_handle handle, int x, int y)
	{
	Desk_menu2_block	*m = (Desk_menu2_block *) handle;
	
	Desk_Debug2_Printf( "Desk_Menu2_Open called\n");
	
	if ( Desk_menu2__numopen > 0)	Desk_Menu2__Release( NULL);
		/*
		Under RO2, the previous menu can have closed without us hearing
		about it.
		 */
	
	Desk_Menu2__Free( 0);	/* Close all menus	*/
	
	Desk_Menu2__EnsureMenu( m);
	Desk_Menu_Show( m->menu, x, y);
	Desk_menu2__openmenus[0] = m;
	Desk_menu2__numopen = 1;
	Desk_Menu2__Claim( NULL);
	}




static int	Desk_Menu2__SelectionDepth( int *selections)
	/* Returns the number of menus in the selection	*/
	{
	int	i;
	for ( i=0; selections[i]!=-1; i++)	;
	Desk_Debug2_Printf( "Desk_Menu2__SelectionDepth, returning %i\n", i);
	return i;
	}




static void	Desk_Menu2__OpenSub( Desk_event_pollblock *event, Desk_menu2_handle handle)
	{
	Desk_menu2_block	*m = (Desk_menu2_block *) handle;
	int			selectdepth = Desk_Menu2__SelectionDepth( &event->data.message.data.words[3]);
	
	Desk_Debug2_Printf( "Desk_Menu2__OpenSub, handle=%i, selectdepth= %i\n", handle, selectdepth);
	
	Desk_Menu2__Free( selectdepth);
		
	Desk_Menu2__EnsureMenu( m);
	Desk_Wimp_CreateSubMenu(
		m->menu,
		event->data.message.data.words[1], 
		event->data.message.data.words[2]
		);
	
	Desk_menu2__openmenus[ Desk_menu2__numopen] = m;
	Desk_menu2__numopen++;
	}





Desk_SDLS_PtrFn( 
	static, 
	Desk_bool, 
	Desk_Menu2__MessageHandler( Desk_event_pollblock *event, void *reference)
	)
/*static Desk_bool	Desk_Menu2__MessageHandler( Desk_event_pollblock *event, void *reference)*/
/* cc warns about extern ... not declared in header in SDLS compiles	*/
	{
	Desk_UNUSED( reference);
	
	Desk_Debug2_Printf( "Desk_Menu2__MessageHandler\n");
	
	if ( event->data.message.header.action == Desk_message_MENUWARN)	{
	
		int			*selections	= &event->data.message.data.words[3];
		int			selectdepth	= Desk_Menu2__SelectionDepth( selections);
		Desk_menu2_block	*lastmenu	= Desk_menu2__openmenus[ selectdepth-1];
		Desk_menu2_handle	nextmenu	= NULL;
		
		Desk_Menu2__Free( selectdepth);
		Desk_Debug2_Printf( "Desk_Menu2__MessageHandler, Desk_menu2__numopen=%i\n", Desk_menu2__numopen);
		
		if ( Desk_menu2__numopen < selectdepth)	return Desk_bool_FALSE;
			/* Some menus are open which we don't know about...	*/
		
		if ( lastmenu->subfn)	{
			Desk_Debug2_Printf( "Desk_Menu2__MessageHandler, calling subfn %p\n", lastmenu->subfn);
			nextmenu = lastmenu->subfn( 
				selections[ selectdepth-1], 
				event, 
				lastmenu->reference
				);
			Desk_Debug2_Printf( "Desk_Menu2__MessageHandler, nextmenu=%i\n", nextmenu);
			if (nextmenu)	Desk_Menu2__OpenSub( event, nextmenu);
			return Desk_bool_TRUE;
			}
		}
	
	else if ( event->data.message.header.action == Desk_message_MENUSDELETED)	{
		Desk_Menu2__Free( 0);
		Desk_Menu2__Release( reference);
		}
	
	else if ( event->data.message.header.action == Desk_message_HELPREQUEST)	{
		// Get state of menu if help request is for the menu...
		int	selections[ 10];
		
		Desk_Error2_CheckOS( _swix( Wimp_GetMenuState, _INR(0,3), 
					1, 
					selections, 
					event->data.message.data.helprequest.where.window, 
					event->data.message.data.helprequest.where.icon
					)
				);
		
		if ( selections[0] == -1)	return Desk_bool_FALSE;	// Help request isn't for current menu.
		
		else	{
			int			selectdepth	= Desk_Menu2__SelectionDepth( selections);
			Desk_menu2_block	*lastmenu	= Desk_menu2__openmenus[ selectdepth-1];
			
			if ( Desk_menu2__numopen < selectdepth)	return Desk_bool_FALSE;
				/* Some menus are open which we don't know about...	*/
			
				{
				// We need to ignore help request if it is for a menu leaf dbox. The
				// only way I know to check for this is to check whether the window
				// handle is know to us... A bit crude but...
				char	templname[ 16];
				Desk_Window_ParentName( event->data.message.data.helprequest.where.window, templname);
				if ( templname[0])	return Desk_bool_FALSE;	// Window is a valid window - eg menu dbox.
				}
			
			if ( lastmenu->helpfn)	{
				Desk_message_block	helpreply;
				helpreply.header.yourref	= event->data.message.header.myref;
				helpreply.header.action		= Desk_message_HELPREPLY;
				helpreply.header.size		= 256;
				lastmenu->helpfn( helpreply.data.helpreply.text, 200, selections[ selectdepth-1], lastmenu->reference);
				Desk_Wimp_SendMessage( Desk_event_USERMESSAGE, &helpreply, event->data.message.header.sender, 0);
				}
			
			return Desk_bool_TRUE;
			}
		}
	
	return Desk_bool_FALSE;
	}



Desk_SDLS_PtrFn( 
	static, 
	Desk_bool, 
	Desk_Menu2__MenuHandler( Desk_event_pollblock *event, void *reference)
	)
	/*static Desk_bool	Desk_Menu2__MenuHandler( Desk_event_pollblock *event, void *reference)*/
	/* cc warns about extern ... not declared in header in SDLS compiles	*/
	{
	int			selectdepth	= Desk_Menu2__SelectionDepth( &event->data.selection[0]);
	Desk_menu2_block	*leafmenu;
	Desk_mouse_block	mouse;
	
	if ( selectdepth>Desk_menu2__numopen)	selectdepth = Desk_menu2__numopen;
	
	leafmenu = Desk_menu2__openmenus[ selectdepth-1];
	
	Desk_Wimp_GetPointerInfo( &mouse);
	
	if ( leafmenu->selectfn)	{
		Desk_Debug2_Printf( "Desk_Menu2__MenuHandler calling selectfn %p\n", leafmenu->selectfn);
		leafmenu->selectfn( event->data.selection[ selectdepth-1], leafmenu->reference);
		Desk_Debug2_Printf( "Desk_Menu2__MenuHandler called selectfn %p\n", leafmenu->selectfn);
		
		if ( !mouse.button.data.adjust)	Desk_Wimp_GetPointerInfo( &mouse);
			/* Have a second chance to use Adjust (eg if selecfn	*/
			/* brought up an error-box.				*/
		}
		/*
		Note that this is not necessarily the leaf menu, just the
		leaf-est menu2 opened. This is because the last menu2 
		could have opened non-menu2 menus itself.
		*/
	
	if ( mouse.button.data.adjust)	{
	
		int	i;
		
		/* Rescan all open menus...	*/
		Desk_Debug2_Printf( "Desk_Menu2__MenuHandler reopening menu\n");
		
		for ( i=0; i<Desk_menu2__numopen; i++)	{
			Desk_menu2_block	*m = Desk_menu2__openmenus[ i];
			if ( m->flagsfn)	{
				Desk_Debug2_Printf( "Desk_Menu2__MenuHandler calling flagsfn\n");
				/*Desk_Debug_Printf( "menublock is %p %p %p %p %p %p %p %p\n",
					m->title, m->spec, m->makefn, m->flagsfn, m->subfn,
					m->selectfn, m->menu, m->reference
					);
				*/
				m->flagsfn( m->menu, m->reference);
				}
			}
			
		Desk_Debug2_Printf( "Desk_Menu2__MenuHandler calling Desk_Menu_ShowLast\n");	
		Desk_Menu_ShowLast();
		}
	
	else	{
		Desk_Menu2__Free( 0);
		Desk_Menu2__Release( reference);
		}
	
	return Desk_bool_FALSE;
	}




static void	Desk_Menu2__ClaimRelease( Desk_event_claimorreleasefn fn, void *reference)
	{
	fn( Desk_event_USERMESSAGE, 		Desk_event_ANY, Desk_event_ANY, Desk_SDLS_dllEntry( Desk_Menu2__MessageHandler), 	reference);
	fn( Desk_event_USERMESSAGERECORDED, 	Desk_event_ANY, Desk_event_ANY, Desk_SDLS_dllEntry( Desk_Menu2__MessageHandler), 	reference);
	fn( Desk_event_MENU, 			Desk_event_ANY, Desk_event_ANY, Desk_SDLS_dllEntry( Desk_Menu2__MenuHandler), 	reference);
	}

