/* POPMENU.C
 *==========================================================================
 * DESCRIPTION: Generic Popup Routines ( New and Improved )
 * VERSION 0.00  Started:	February 7, 1991
 * VERSION 0.10			March    4, 1991
 * version 1.00			April    24,1991
 *				Recreated damaged files
 *				Removed open workstation calls.
 *				vhandle = graf_handle()
 *
 *
 * INCLUDE: POPMENU.H
 * Requires: POP_HEAD.H
 *
 */



/* INCLUDE FILES
 *==========================================================================
 */
#include <sys\gemskel.h>
#include <string.h >
#include <tos.h>
#include <stdlib.h>
#include <stdio.h>

#include "pop_head.h"
#include "device.h"


extern int xres, yres;


/* PROTOTYPES
 *==========================================================================
 */
void	InitPopUpMenus( void );
int	GetNewPopMenu( void );
POP_PTR	GetMenuPtr( int MenuID );

BOOLEAN	InitCmdChar( POP_PTR PopPtr );
void	BuildCmdChar( POP_PTR PopPtr );
void	BuildKeyCodes( POP_PTR PopPtr, char *text, int num_entry );
void	DeletePopUpMenu( int MenuID );

int     InsertPopUpMenu( char *text, int num_entries, int height );
long    PopUpMenuSelect( int MenuID, int xpos, int ypos, int Item );
BOOLEAN Pop_Blit( long *PopPtr, GRECT *xclip, int flag );


BOOLEAN Build_Objects( POP_PTR curptr );
void    SetRootNode( OBJECT *tree, int xpos, int ypos, int Width, int Num );
void    SetTextNode( OBJECT *tree, int parent, POP_PTR PopPtr, int obj, int Ypos, int textnum, int Width );
long	CalcTextBufferSize( POP_PTR PopPtr );
void	BuildText( POP_PTR PopPtr );
int	CountBlanksNeeded( char *dindex, char *dtemp, int width, int num );


BOOLEAN	Pop_Arrow( POP_PTR PopPtr, int obj );
void	UpArrowStatus( POP_PTR PopPtr );
void	DownArrowStatus( POP_PTR PopPtr );
void	SetArrowClickDelay( long delay );

/* KEYCODE CHECKING */
long	CheckForKeyCodes( POP_PTR PopPtr, char *text, int num_entry );
char	*CheckForCtrl( char *text );
char    *CheckForAlt( char *text );
char    *CheckForCapKey( char *text );
char	*CheckForCmdKey( char *text );
BOOLEAN CheckForDisable( char *text );
BOOLEAN	CheckForMenuCheck( char *text );
char 	*CheckForSubMenu( char *text );
char	*CheckForFuncKey( char *text );
BOOLEAN	CheckForDuplicate( char *text );
   
/* APPEARANCE OF ITEMS */
void	SetHeight( int MenuID, int Height );
void	SetNumItems( int MenuID, int NumItems );

void	CheckItem( int MenuID, int item, BOOLEAN check );
void	DisableItem( int MenuID, int item );
void	EnableItem( int MenuID, int item );
   
void	SetItemCmd( int MenuID, int item, char cmd );
char	GetItemCmd( int MenuID, int item );

void	SetSubMenuID( int MenuID, int item, int ID );
int	GetSubMenuID( int MenuID, int item );

void	SetItemMark( int MenuID, int item, char key );
char    GetItemMark( int MenuID, int item );
 
void	SetFuncMark( int MenuID, int item, int num );
int 	GetFuncMark( int MenuID, int item );

void	SetItem( int MenuID, int item, char *text );
char	*GetItem( int MenuID, int item );

int	GetStartItem( int MenuID );
void	SetStartItem( int MenuID, int item );


/* SUBMENU HANDLING */
POP_PTR	DoSubMenu( POP_PTR PopPtr, int obj );
BOOLEAN	ShowSubMenu( int MenuID, int xpos, int ypos, GRECT *rect );
void	HideSubMenu( POP_PTR PopPtr );
long	EvntSubMenu( POP_PTR PopPtr );
int	FindNum( POP_PTR PopPtr, int obj );
long    MenuChoice( void );


/* SUBMENU DELAY */
long	GetTimeHz( void );
void	SetSubMenuDelay( long ms );
void	SetSubDragDelay( long ms );


void	AdjustToScreen( POP_PTR PopPtr, int xpos, int ypos, GRECT *rect, BOOLEAN byte_flag, BOOLEAN Display_Flag );
void	WaitForUpButton( void );

/* EXTERNALS
 *==========================================================================
 * These functions and variables are required and must be
 * supplied externally.
 */
 
int	BUT_OR( void );		/* Used to ensure left mouse button */
extern  int   (*BADDR)( void );
extern  int	BSTATE;
extern  int	BREAL;


/* DEFINES
 *==========================================================================
 */
#define INIT_DISPLAY_DELAY  300L    /* Initial Submenu Display Delay   */ 
#define INIT_DRAG_DELAY     3000L   /* Initial Submenu Drag Delay      */

#define INIT_CLICK_DELAY    150L   /* Delay for STARTING arrow scroll */

#define STLOW	2
#define STMED	3

#define MAX_MENUS	50	   /* Maximum # of Menus allowed...*/
#define UP_ARROW	0x01
#define DOWN_ARROW	0x02
#define RIGHT_ARROW	0x03



/* GLOBAL
 *==========================================================================
 */
POP_NODE MenuList[ MAX_MENUS ];		/* Menu structure Storage */
BOOLEAN  free_flag[ MAX_MENUS ];

char *KeyText[] = { "HOME",		/* Text for menu items using these*/
		    "HELP",		/* as keyboard shortcuts...       */
		    "UNDO",
		    "ESC",
		    "INSERT",
		    "CLR",
		    "DEL",
		    "TAB",
		    "ENTER",
		    "RETURN"
		  };


char CmdText[ 10 ];		/* Temp string storage...       */
char TempString[ 255 ];
int  xout[57];		    /* used for vq_extnd() */


long SUBMENU_DELAY;	    /* Delay time for submenus to appear ( ms )    */
long SUBDRAG_DELAY;	    /* Delay time for submenus to go active( ms)   */
long CLICK_DELAY;	    /* Delay time for arrows to start scrolling(ms)*/
long TimeInHz;		    /* Current Time in 200 Hz */

			    /* Used by the MenuChoice() call         */
int  CurObject;		    /* Current Object clicked on. ( Global ) */
int  CurMenu;		    /* Current Menu clicked on. ( Global )   */


/* FUNCTIONS
 *==========================================================================
 */


/* InitPopMenus()
 * ====================================================================
 * Simply clears out the flags in the free_flags structure.
 */
void
InitPopUpMenus( void )
{
   int i;
   
   for( i = 0; i < MAX_MENUS; i++ )
      free_flag[ i ] = FALSE;
   SetSubMenuDelay( INIT_DISPLAY_DELAY );
   SetSubDragDelay( INIT_DRAG_DELAY );		
   SetArrowClickDelay( INIT_CLICK_DELAY );
}



/* InsertPopMenu()
 * ====================================================================
 * Returns: Pointer to the POPUP structure.
 * IN:   
 *       XHEIGHT ( in characters ) - actually, number of entries displayed
 *	 NUM_ENTRIES		   - Total # of menu entries.
 *	 Pointer to text address...
 * OUT: # >= 0    Valid Menu ID
 *      # < 0     Error messages.
 *                -1 = Memory Allocation Error.
 *		  -2 = No more Menu Slots available.
 */
int
InsertPopUpMenu( char *text, int num_entries, int height )
{
    POP_PTR PopPtr;
    int     MenuID;
    
    MenuID = GetNewPopMenu();
    
    /* No more menu slots available.*/
    if( MenuID == -1 )
       return( -2 );

    PopPtr = GetMenuPtr( MenuID );
    PPREV( PopPtr )   = ( POP_PTR )NULL;
       
    PMENUID( PopPtr ) = MenuID;     
    PXPOS( PopPtr )   = PYPOS( PopPtr ) = 0;
    PNUM( PopPtr )    = num_entries;
    POBJECT( PopPtr ) = ( OBJECT *)NULL;
    
    /* Take care of height of menu */ 
    if( height > PNUM( PopPtr ) )
        height = PNUM( PopPtr );
    PHEIGHT( PopPtr )     = height;
    PIX_HEIGHT( PopPtr )  = ( PHEIGHT( PopPtr ) * gl_hchar );
    PTEXT( PopPtr )       = text;
    
    if( !InitCmdChar( PopPtr ))
    {
       /* If memory error, clear out the Menu Slot
        * and return a -1.
        */
       free_flag[ MenuID ] = FALSE;       
       MenuID = -1;
    }
    return( MenuID );
}



/* GetNewPopMenu()
 * ====================================================================
 * Find an unused slot for a menu.
 * RETURN( -1 ) if there are no more menu slots available.
 */
int
GetNewPopMenu( void )
{ 
    int i;
    
    for( i = 0; i < MAX_MENUS; i++ )
    {
	if( !free_flag[ i ] )
	{
	   free_flag[i] = TRUE;
	   return( i );
	}   
    }
    return( -1 );
}




/* GetMenuPtr()
 * ====================================================================
 * Returns a pointer to the Menu Structure if given a valid menuID
 */
POP_PTR
GetMenuPtr( int MenuID )
{
    return( ( POP_PTR )&MenuList[ MenuID ] );
}



/* InitCmdChar()
 * ====================================================================
 * Initializes the CmdChar memory for a menu structure.
 * The memory is MALLOCed! So the application MUST call
 * DeletePopMenu() for each menu created. OR ELSE...!
 * Need to check for malloc failures.
 * RETURN: FALSE = ERROR!
 *	   TRUE  = AOK!
 * It will be up to the program to inform the user that
 * no menu was created and initialized.
 */
BOOLEAN
InitCmdChar( POP_PTR PopPtr )
{
    int i;
    
    PCmdChar( PopPtr ) = ( CmdChar *)malloc( (long)( (long)PNUM( PopPtr ) + 10L ) * (long)sizeof( CmdChar ));
    /* We go up to + 2 because we have ...
     * PNUM() = UP ARROW TEXT
     * PNUM+1 = DOWN ARROW TEXT
     * PNUM+2 = BLANK TEXT
     */
    if( PCmdChar( PopPtr ) )
    { 
      for( i = 0; i <= ( PNUM( PopPtr ) + 2 ); i++ )
      {
         CmdState( PopPtr->CharPtr[ i ] ) = NORMAL;
         CmdSubMenu( PopPtr->CharPtr[ i ] ) = -1;
         CmdFlag( PopPtr->CharPtr[i] ) = '\0';
      }
      
      BuildCmdChar( PopPtr );
      return( TRUE );
    }
    else
      return( FALSE );
}




/* BuildCmdChar()
 * ====================================================================
 * Calculate the text buffer size that we'll have to malloc, based upon
 * the largest text string and the number of entries.
 */
void
BuildCmdChar( POP_PTR PopPtr )
{
   char *txtptr;
   long length;
   int  count;
   
   txtptr = PTEXT( PopPtr );
   if( txtptr )
   {
       for( count = 0; count < PNUM( PopPtr ); count++ )
       {    
         length = strlen( txtptr );
         BuildKeyCodes( PopPtr, txtptr, count );
         txtptr += ( length + 1 );
       }
   }
}





/* BuildKeyCodes()
 * =====================================================================
 * Checks the text string for various keycodes and subtracts
 * an appropriate amount from the length if any are found.
 * For many of these, there should only be one of them.
 * We then ADD back in the amount of ACTUAL text usage this item
 * would take. IE: a Function Key can be F10 so 3 characters...
 * or a CmdKey can be the letters: 'DELETE' 6 characters added.
 * This will also fill up the CMDCHAR structures.
 */
void
BuildKeyCodes( POP_PTR PopPtr, char *text, int num_entry )
{
    char *txtptr;

    if( ( txtptr = CheckForCtrl( text )) != NULL )
    {
        CmdFlag( PopPtr->CharPtr[ num_entry ] ) = '^';
        CmdHotKey( PopPtr->CharPtr[ num_entry ] ) = *( ++txtptr );
    }
    
    
    if( ( txtptr = CheckForAlt( text ) ) != NULL )
    {
         CmdFlag( PopPtr->CharPtr[ num_entry ] ) = '&';
         CmdHotKey( PopPtr->CharPtr[ num_entry ] ) = *( ++txtptr );
    }
         
    if( ( txtptr = CheckForCapKey( text ) ) != NULL )
    {
         CmdFlag( PopPtr->CharPtr[ num_entry ] ) = '|';
         CmdHotKey( PopPtr->CharPtr[ num_entry ] ) = *( ++txtptr );
    }
    
    if( ( txtptr = CheckForCmdKey( text ) ) != NULL )
    {
        /* We ACKNOWLEDGE -----
         * [H] HELP	[U] Undo	[E] Escape	[I] Insert
         * [D] DEL	[C] Clr		[h] Home	[T] TAB
         * [e] ENTER	[R] RETURN
         */
        CmdFlag( PopPtr->CharPtr[ num_entry ] ) = '$';
        CmdHotKey( PopPtr->CharPtr[ num_entry ] ) = *( ++txtptr ); 
    }
    
    
    /* Check for the '@' character which will tell us to
     * disable the current menu item that we are working on.
     */
    
    if( CheckForDisable( text ) )
      DisableItem( PMENUID( PopPtr ), num_entry );
    else   
      EnableItem( PMENUID( PopPtr ), num_entry );

    /* Check for CheckMark  ( ! ) */
    CheckItem( PMENUID( PopPtr ), num_entry, CheckForMenuCheck( text ) );

    if( ( txtptr = CheckForSubMenu( text ) ) != NULL )
    {
	CmdFlag( PopPtr->CharPtr[ num_entry ] ) = '~';
	txtptr++;
	CmdSubMenu( PopPtr->CharPtr[ num_entry ] ) = atoi( txtptr );
    } 
    
    if( ( txtptr = CheckForFuncKey( text ) ) != NULL )
    {
        CmdFlag( PopPtr->CharPtr[ num_entry ] ) = '#';
        txtptr++;
        CmdFuncKey( PopPtr->CharPtr[ num_entry ] ) = atoi( txtptr );
    }
    
    if( CheckForDuplicate( text ) )
        CmdFlag( PopPtr->CharPtr[ num_entry ] ) = '*';
}






/* DeletePopUpMenu()
 * ====================================================================
 */
void
DeletePopUpMenu( int MenuID )
{
   POP_PTR PopPtr;
   
   if( free_flag[ MenuID ] )
   {
     free_flag[ MenuID ] = FALSE;
     PopPtr = GetMenuPtr( MenuID );
     
     if( PCmdChar( PopPtr ) )
       free( PCmdChar( PopPtr ) );
       
     PCmdChar( PopPtr ) = ( CmdChar *)NULL;
   }  
}




/* PopUpMenuSelect()
 * ====================================================================
 * Displays the actual popup menu.
 * Let's the user select an object.
 * IN:   POP_PTR PopPtr;
 *       int     xpos;
 *       int     ypos;
 *       int     Start_Obj;  Index to show selected else start with 0
 *       BOOLEAN Arrow_Flag; If TRUE, display arrows if height < num_items.
 *			        FALSE, don't display arrows if height < num_items.
 * returns:  long where....
 *		HIGH WORD	LOW WORD
 *		 MenuID	      Item Selected.
 *           -1L if NONE selected.
 *	     If the MenuID is valid, but the item is -1, then
 *	     the user clicked on a disabled menu item.
 *	     -2L if Memory Allocation Error
 *	     -3L if workstation error.
 */
long
PopUpMenuSelect( int MenuID, int xpos, int ypos, int Item )
{
    
    long    obj;
    POP_PTR PopPtr;
    GRECT   rect;
    MRETS   mk;
    int     max_height;
        
    WaitForUpButton();
    wind_update( BEG_MCTRL );

    open_vwork();
    vq_extnd( vhandle, 0, xout );
    xres = xout[0];
    yres = xout[1];    

    vex_butv( vhandle, BUT_OR, &BADDR );    
    
    PopPtr = GetMenuPtr( MenuID );
    PopUpItem( PopPtr ) = Item;

    PXPOS( PopPtr )   = xpos;
    PYPOS( PopPtr )   = ypos;
    PPREV( PopPtr )   = ( POP_PTR )NULL;

    /* Take care of height of menu */ 
    if( PHEIGHT( PopPtr ) > PNUM( PopPtr ) )
        PHEIGHT( PopPtr ) = PNUM( PopPtr );

    /* Limit Height to Height of screen - ( 2 * gl_hchars ) */
    max_height = yres - ( 2 * gl_hchar );
    while( ( PHEIGHT( PopPtr ) * gl_hchar ) > max_height )
       PHEIGHT( PopPtr ) -= 1;

    PIX_HEIGHT( PopPtr )  = ( PHEIGHT( PopPtr ) * gl_hchar );

       
    /* Take care of the width of the menu here...*/
    PTEXTBUFFSIZE( PopPtr ) = CalcTextBufferSize( PopPtr );
    PIX_WIDTH( PopPtr )     = ( PWIDTH( PopPtr ) * gl_wchar );

    AdjustToScreen( PopPtr, xpos, ypos, &rect, FALSE, TRUE );
    if( Pop_Blit( (long *)&PMEM( PopPtr ), &PObRect( PopPtr ), 0 ) )
    {
       if( Build_Objects( PopPtr ) )
       {
         obj = EvntSubMenu( PopPtr );
         if( obj == -2L ) /* returning from a submenu - error */
             obj = -1L;	/* then set it to click on nothing */
         Pop_Blit( (long *)&PMEM( PopPtr ), &PObRect( PopPtr ), 1 );
       }
       else    /* Memory Allocation Error in Space Capsule */
         obj = -2L;  

       if( POBJECT( PopPtr ) )
           free( POBJECT( PopPtr ) );
        
       if( PTEXTBUFF( PopPtr ) )    
           free( PTEXTBUFF( PopPtr ) );
        
    }
    else	/* ERROR: Memory Allocation Error */
      obj = -2L;

    POBJECT( PopPtr )   = ( OBJECT *)NULL;
    PTEXTBUFF( PopPtr ) = ( char *)NULL;
    
    Evnt_button( 1, 1, 0, &mk );

    vex_butv( vhandle, BADDR, &BADDR );
    close_vwork();
    
    wind_update( END_MCTRL );

    return( obj );
}
 
 


/* Build_Objects()
 * ====================================================================
 * RETURN: TRUE - AOK
 *         FALSE- Memory Allocation Error
 */
BOOLEAN
Build_Objects( POP_PTR PopPtr )
{
   int     num_objects;
   GRECT   rect;
   int     text_index;		/* Index into the text data 0 based */
   int     count;
   int     ypos;		/* Y position of object ( in characters*/
   int     temp;
          
   OBJECT *tree;
   int    obj;

   /* Calculate the number of objects needed...
    * ROOT + # of STRINGS + ( safety buffer )
    */
   num_objects = 1 + PHEIGHT( PopPtr ) + 10; /* + 10 for safety buffer */
 
   /* get the memory for this number of objects */   
   POBJECT( PopPtr ) = ( OBJECT * )malloc( (long)(sizeof( OBJECT ) * (long)num_objects )); 
    
   /* Malloc the text memory and then build them. */
   /* Need malloc checking of course...           */
   PTEXTBUFF( PopPtr) = malloc( PTEXTBUFFSIZE( PopPtr ) );
   
   /* Memory Allocation Error */
   if( !POBJECT( PopPtr ) || !PTEXTBUFF( PopPtr ) )
       return( FALSE );

   BuildText( PopPtr );

       
   /* setup the root */
   tree = POBJECT( PopPtr );
   SetRootNode( tree, PXPOS( PopPtr ), PYPOS( PopPtr ),
                PWIDTH( PopPtr ), PHEIGHT( PopPtr ) );
   
   ypos = text_index = 0;
   
   obj = 1;			/* Start Creating from object 1 */
   PFIRST( PopPtr ) = 1;	/* first G_STRING starts at 3   */

   POFFSET( PopPtr )   = ( PopUpItem( PopPtr ) + ( PNUM( PopPtr ) > PHEIGHT( PopPtr ) )) / PHEIGHT( PopPtr );

   if( POFFSET( PopPtr ) )
   {
       POFFSET( PopPtr ) = PopUpItem( PopPtr );
       temp = PNUM( PopPtr ) - PHEIGHT( PopPtr ) + 1;

       if( POFFSET( PopPtr ) >= temp )
           POFFSET( PopPtr ) = temp;
   }      
   text_index = POFFSET( PopPtr );
   
   for( count = 0; count < PHEIGHT( PopPtr ); count++ )
   {
      if( count == 0 )
      {
	   if( POFFSET( PopPtr ) )
             SetTextNode( tree, ROOT, PopPtr, obj++, ypos++, PNUM( PopPtr ), PWIDTH( PopPtr ) );
           else  
           {
             if( CmdState( PopPtr->CharPtr[ text_index ] ) & CHECKED )
                 CheckObj( obj );
             SetTextNode( tree, ROOT, PopPtr, obj++, ypos++, text_index++, PWIDTH( PopPtr ) );
           }  
      }  
      else
      {
        if( count >=  ( PHEIGHT( PopPtr ) - 1 ) )
        {
           if( ( POFFSET( PopPtr ) + PHEIGHT( PopPtr ) ) < PNUM( PopPtr ) )
              SetTextNode( tree, ROOT, PopPtr, obj++, ypos++, PNUM( PopPtr ) + 1, PWIDTH( PopPtr ) );
           else
           {
              if( CmdState( PopPtr->CharPtr[ text_index ] ) & CHECKED )
                 CheckObj( obj );

              SetTextNode( tree, ROOT, PopPtr, obj++, ypos++, text_index++, PWIDTH( PopPtr ) );
           }   
        }
        else      
        {
           if( CmdState( PopPtr->CharPtr[ text_index ] ) & CHECKED )
               CheckObj( obj );

           SetTextNode( tree, ROOT, PopPtr, obj++, ypos++, text_index++, PWIDTH( PopPtr ) );
        } 
      }
   }   
   PLAST( PopPtr ) = obj - 1;
   ObFlags( obj - 1 ) = LASTOB;

   rect = ObRect( ROOT );
   rect.g_x -= 2;
   rect.g_y -= 2;
   rect.g_w += 5;
   rect.g_h += 5;
   rc_intersect( &desk, &rect );        /* clip to desktop */
   Objc_draw( tree, ROOT, MAX_DEPTH, &rect );
   return( TRUE );
}



/* SetRootNode()
 * ====================================================================
 */
void
SetRootNode( OBJECT *tree, int xpos, int ypos, int Width, int Num )
{
    ObNext( ROOT )  = -1;
    ObHead( ROOT )  = -1;
    ObTail( ROOT )  = -1;
    ObType( ROOT )  = G_BOX;
    ObFlags( ROOT ) = NONE;
    ObState( ROOT ) = SHADOWED;
    ObIndex( ROOT ) = 0xFF1100L;

    /* fix up for screen...*/
    ObX( ROOT ) = xpos;
    ObY( ROOT ) = ypos;
    ObW( ROOT ) = Width * gl_wchar;
    ObH( ROOT ) = Num * gl_hchar;
}



/* SetTextNode()
 * ====================================================================
 */
void
SetTextNode( OBJECT *tree, int parent, POP_PTR PopPtr, int obj, int Ypos, int textnum, int Width )
{
       ObNext( obj ) = -1;
       ObHead( obj ) = -1;
       ObTail( obj ) = -1;

       ObType( obj )   = G_STRING;
       ObFlags( obj )  = NONE;
       
       ObString( obj ) = &PTEXTBUFF( PopPtr )[ ( PWIDTH( PopPtr ) + 1 ) * textnum ];

       ObState( obj )  = CmdState( PopPtr->CharPtr[ textnum ] );

       ObX( obj )      = 0;
       ObY( obj )      = Ypos * gl_hchar;
       ObW( obj )      = Width * gl_wchar;
       ObH( obj )      = gl_hchar;
       objc_add( tree, parent, obj );
}




/* CalcTextBufferSize()
 * ====================================================================
 * Calculate the text buffer size that we'll have to malloc, based upon
 * the largest text string and the number of entries.
 */
long
CalcTextBufferSize( POP_PTR PopPtr )
{
   char *xptr;
   long size;
   long xsize;
   long actual;
   int  i;
   
   size = 0L;
   PWIDTH( PopPtr ) = 0;
   xptr = ( char *)NULL;
   xptr = PTEXT( PopPtr );
   if( xptr )
   {
       for( i = 0; i < PNUM( PopPtr ); i++ )
       {    
         xsize = strlen( xptr );
         actual = CheckForKeyCodes( PopPtr, xptr, i );
         if( actual > size )
            size = actual;
         xptr += ( xsize + 1 );

         /* Get's us past the nulls to the head of the next string */
         while( !( *xptr ) )
           xptr++;         
       }
       /* size-> now contains the length of the longest string. */
       size += 1;              /* for a blank after...*/
       size += 2;	       /* for 2 blanks before the string*/
       /* Number of characters in our longest string...*/
       PWIDTH( PopPtr ) = (int)size;

       size += 1;	        /* for the null.       */
       size += 10;	        /* for the Control Keys or RT Arrow */
       size *= PNUM( PopPtr );  /* For the number of entries. */
       size += ( size / 2 );    /* Buffer area, just in case  */  
   }
   return( size );
}

 
/* BuildText()
 * ====================================================================
 * Takes the pointer to the menu_text given by the program and
 * builds the text in the memory buffer where...
 *  1) 2 spaces before each text string.
 *  2) 1 space after the longest string.
 *  3) pads spaces after shorter strings to match the longest string.
 *  4) Null terminates each string.
 *  5) *# - The char following is duplicated the width of the menu.
 *	    Characters following it are ignored to the end of the string.
 *	    example: *-    The line is filled with "---------"
 *  6) @  - means to disable this menu item
 *  7) /# - means that there is a keycode: ie: /T ==   [T]
 *  8) ## - means that there is a function key.  #10 is F10
 *  9) ^# - means that there is a ctrl key:  ^H
 * 10) $# - means that there is a CmdKey: ie: $H = HELP ( 4 chars )
 * 	We ACKNOWLEDGE -----
 * 	[H] HELP	[U] Undo	[E] Escape	[I] Insert
 * 	[D] DEL		[C] Clr		[h] Home	[T] TAB
 * 	[e] ENTER	[R] RETURN
 *
 * 11) &# - means that there is an ALT Key:  &A - Alt A
 * 12) ~# - means that there is a submenu: ~1 - attach submenu 1
 * 13) !  - means that the menu item is CHECKED.
 */
void
BuildText( POP_PTR PopPtr )
{
   char *sindex;		/* Pointer to source text...    */
   char *stemp;			/* Pointer INTO source text     */
   char *dindex;		/* Pointer to destination text  */
   char *dtemp;			/* Pointer INTO destination text*/
   int  num_entries;		/* Number of entries counter... */
   int  num;
   int  num2;
   long length;			/* length of a string...	*/
   long i;			/* temp variable...		*/
   
   if( !PTEXTBUFF( PopPtr ) )	/* if there is no destination buffer...*/
       return;			/* skip doing all of this...           */
          
   sindex = PTEXT( PopPtr );
   dindex = PTEXTBUFF( PopPtr );

   for( num_entries = 0; num_entries <= PNUM( PopPtr ); num_entries++ )
   {
     if( num_entries < PNUM( PopPtr ) )
     {
     
       if( ( stemp = strchr( sindex, '*' )) != NULL )
       {
         /* Check for a '*' character. The character following
          * will be extened the length of the string.
          * All characters following after that will be ignored.
          */
         stemp++;	 /* gets us to the item we want to duplicate */ 
         dtemp = dindex; /* get a ptr to the destination             */

	 /* Duplicate the character to the proper width wanted...*/         
         for( i = 0; i < PWIDTH( PopPtr ); i++ )
            *dtemp++ = *stemp;
         *dtemp = '\0';   	/* NULL terminate the string */
       }  
       else
       {
         strcpy( dindex, "  " );	/* Pad the entry with 2 blanks */
         stemp = sindex;		/* Get a pointer to the source */
         dtemp = dindex+2;		/* Get a pointer to the dest...*/

	 /* Copy the source string to the destination buffer
	  * skipping the HOT chars that will allow submenus,
	  * disabling menus, keycodes etc.
	  */
	 while( *stemp )
	 {
	    switch( *stemp )
	    {
	       case '@': /* Disable menu item */
	       case '!': /* CheckMark menu item */
	       		 stemp++;
	       		 break;
	
	       case '^': /* Ctrl Key */
	       case '&': /* Alt Key  */
	       case '|': /* Cap Key  */
	       case '$': /* CmdKey   */
	       case '~': /* Submenu  */
	       		 stemp += 2;
	       		 break;
	       		 
	       case '#': /* FuncKey  */
	       		 stemp += 2;
	       		 num = CmdFuncKey( PopPtr->CharPtr[ num_entries ] );
	       		 if( num > 9 )
	       		    stemp++;
	       		 break;
	       		 		
	       default: *dtemp++ = *stemp++;
	       		break;
	    }
	 }

	 /* Now add in the necessary text for the Keycodes..*/
         switch( CmdFlag( PopPtr->CharPtr[ num_entries ] ) )
         {
	       case '^': /* Ctrl Key */
	       case '&': /* Alt Key  */
			 num = CountBlanksNeeded( dindex, dtemp, PWIDTH( PopPtr ), 2 );
			 for( i = 0; i < num; i++ )	       		 
			    *dtemp++ = ' ';     /* add a space */
	                 *dtemp++ = (( CmdFlag( PopPtr->CharPtr[ num_entries ] ) == '^' ) ? ( '^' ) : ( 0x07 ) );   
	                 *dtemp++ = CmdHotKey( PopPtr->CharPtr[ num_entries ] );
	       		 break;
	       		 
	       case '|': /* Cap Key  */
			 num = CountBlanksNeeded( dindex, dtemp, PWIDTH( PopPtr ), 3 );
			 for( i = 0; i < num; i++ )	       		 
			    *dtemp++ = ' ';     /* add a space */
	       		 *dtemp++ = '[';
	       		 *dtemp++ = CmdHotKey( PopPtr->CharPtr[ num_entries ] );
	       		 *dtemp++ = ']';
	       		 break;
	       		 
	       case '$': /* CmdKey   */
	       		 switch( CmdHotKey( PopPtr->CharPtr[ num_entries ] ) )
	       		 {
	       		    case 'h': /* Home */
	       		    	      strcpy( &CmdText[0], KeyText[0] ); 
	       		    	      break;
	       		    	      
	       		    case 'H': /* HELP */
	       		    	      strcpy( &CmdText[0], KeyText[1] );
	       		    	      break;
	       		    	      
	       		    case 'U': /* Undo */
	       		    	      strcpy( &CmdText[0], KeyText[2] );
	       		    	      break;
	       		    	      
	       		    case 'E': /* Escape*/
	       		    	      strcpy( &CmdText[0], KeyText[3] );
	       		    	      break;
	       		    	      
	       		    case 'I': /* Insert */
	       		    	      strcpy( &CmdText[0], KeyText[4] );
	       		    	      break;
	       		    	      
	       		    case 'C': /* Clr */
	       		    	      strcpy( &CmdText[0], KeyText[5] );
	       		    	      break;
	       		    	      
	       		    case 'D': /* Delete */
	       		    	      strcpy( &CmdText[0], KeyText[6] );
	       		    	      break;
	       		    	      
			    case 'T': /* TAB    */
	    			      strcpy( &CmdText[0], KeyText[7] );
			     	      break;

			    case 'e': /* ENTER  */
	    			      strcpy( &CmdText[0], KeyText[8] );
			     	      break;

			    case 'R': /* RETURN */	    
			    	      strcpy( &CmdText[0], KeyText[9] );
			     	      break;

	       		    default:  CmdText[0] = '\0';
	       		    	      break;
	       		 }
	       		 num = (int)strlen( &CmdText[0] );
			 num = CountBlanksNeeded( dindex, dtemp, PWIDTH( PopPtr ), num );
			 for( i = 0; i < num; i++ )	       		 
			    *dtemp++ = ' ';     /* add a space */
			 *dtemp = '\0';   
			 strcat( dindex, &CmdText[0] );
			 length = strlen( dindex );
			 dtemp = dindex + length;			 
	       		 break;
	       		 
	       case '#': /* FuncKey  */
			 num = CmdFuncKey( PopPtr->CharPtr[ num_entries ] );
			 num = (( num < 10 ) ? ( 2 ) : ( 3 ) );
			 num2 = CountBlanksNeeded( dindex, dtemp, PWIDTH( PopPtr ), num );
			 for( i = 0; i < num2; i++ )	       		 
			    *dtemp++ = ' ';     /* add a space */
	       		 *dtemp++ = 'F';
	       		 num = CmdFuncKey( PopPtr->CharPtr[ num_entries ] );
	       		 if( num < 10 )
	       		    *dtemp++ = num + '0';
	       		 else	/* handle special case for F10 */
	       		 {
	       		    *dtemp++ = '1';
	       		    *dtemp++ = '0';
	       		 }   
	       		 break;

	      case '~': /* Submenu */
			num = CountBlanksNeeded( dindex, dtemp, PWIDTH( PopPtr ), 2 );
			for( i = 0; i < num; i++ )	       		 
			    *dtemp++ = ' ';     /* add a space */
	                *dtemp++ = RIGHT_ARROW;	/* Right Arrow */   
	                *dtemp++ = ' ';
	     	       break;
	     	                
             default:
             	      break;
	 }
	 *dtemp = '\0';         
      
      	 /* Get the length of the destination string and
      	  * pad it with blanks to make it the width of 
      	  * the final string wanted.
      	  */
         length = strlen( dindex );
         for( i = length; i < PWIDTH( PopPtr ); i++ )
            strcat( dindex, " " );
            
       }
       /* Get the length of the actual string and
        * advance it to the next string skipping the NULL
        * and moving to the first char of the next string.
        */
       length = strlen( sindex );
       sindex = sindex + length + 1;
       
       /* Advance us thru the nulls to the start of the next string*/
       while( !(*sindex) )
         sindex++;

       /* Advance dest to end of the new string */   
       dindex = dindex + PWIDTH( PopPtr ) + 1;
     }
     else
     {
       /* The PNUM() menu item will have an UP Arrow in its text */
       strcpy( dindex, " " );
       for( i = 1; i < PWIDTH( PopPtr ); i++ )
          strcat( dindex, " " );  
       *(dindex + 2 ) = UP_ARROW;	/* UP ARROW */
          
       /* The PNUM() + 1 menu item will have a DOWN Arrow in its text */
       dindex = dindex + PWIDTH( PopPtr ) + 1;
       strcpy( dindex, " " );
       for( i = 1; i < PWIDTH( PopPtr ); i++ )
          strcat( dindex, " " );  
       *(dindex + 2 ) = DOWN_ARROW;	/* DOWN ARROW */       
       
       /* The PNUM() + 2 menu item will have a blank string in its text*/
       dindex = dindex + PWIDTH( PopPtr ) + 1;
       strcpy( dindex, " " );
       for( i = 1; i < PWIDTH( PopPtr ); i++ )
          strcat( dindex, " " );  
     }     
   }
}



/* CountBlanksNeeded()
 * ====================================================================
 * Counts the number of blanks to go between the menu item and the
 * control key or whatever.
 */
int
CountBlanksNeeded( char *dindex, char *dtemp, int width, int num )
{
    long length;
    
    *dtemp   = '\0';
    length = strlen( dindex );
    return( width - 1 - (int)length - num );
}




/* Pop_Arrow()
 *==========================================================================
 * Scroll the popup menu items if up or down arrow is selected.
 * 
 * IN: int obj:		up or down arrow is selected.
 *     int *offset:	Offset into the menu items text array
 *     int num_items:   Total number of menu items involved.
 *     char *items[]:   Pointer to the text array
 *
 * OUT: TRUE - ended on a submenu
 *	FALSE - ended on a normal menu item.
 */
BOOLEAN
Pop_Arrow( POP_PTR PopPtr, int obj )
{
   OBJECT *tree;
   int    DrawFlag;
   int    i,j;
   GRECT  rect;
   GRECT  xrect, clip;
   MRETS  mk;
   int    pxy[8];
   int    xclip[4];
   long   location = 0L;	/* SCREEN MFDB		*/
   BOOLEAN DelayFlag;
   BOOLEAN ReturnFlag;
                 
   tree = POBJECT( PopPtr );

   xrect = ObRect( obj );
   objc_offset( tree, obj, &xrect.g_x, &xrect.g_y );
   DelayFlag  = TRUE;
   ReturnFlag = FALSE;
   
   do
   {
       BSTATE = 0;
       DrawFlag = FALSE;
       /* Up Arrow Selected AND not at the top */
       if( ( obj == PFIRST( PopPtr ) ) && ( POFFSET( PopPtr ) > 0 ))
       {
         POFFSET( PopPtr ) -= 1;
         DrawFlag = TRUE;
         /* special handling...to make it jump to zero if
          * we are displaying item 2, we can jump to zero, blit and
          * redraw the top 2 items instead.
          */
         if( POFFSET( PopPtr ) == 1 )
             POFFSET( PopPtr ) = 0;	
       }

       /* DOWN Arrow and not within PHEIGHT() items from the bottom */
       if( ( obj == PLAST( PopPtr ) ) && ( POFFSET( PopPtr ) <= ( PNUM( PopPtr ) - PHEIGHT( PopPtr ) ) ) )
       {
         POFFSET( PopPtr ) += 1;
         DrawFlag = TRUE;
         /* Special handling for first item only */
         if( POFFSET( PopPtr ) == 1 )
         {
           if( ( POFFSET( PopPtr ) + PHEIGHT( PopPtr ) - 1 ) < PNUM( PopPtr ) )
                 POFFSET( PopPtr ) += 1;
         }
       }

       j = POFFSET( PopPtr );
       if( DrawFlag )
       {
          UpArrowStatus( PopPtr );
      	  DownArrowStatus( PopPtr );      

          /* Get the Blit Rectangle */
          clip = ObRect( PFIRST( PopPtr ));
          objc_offset( tree, ROOT, &clip.g_x, &clip.g_y );
          clip.g_h *= PHEIGHT( PopPtr );
      
          /* Calculate the height */
      
          if( strchr( ObString( PFIRST( PopPtr ) ), UP_ARROW ) )
          {
             clip.g_h -= gl_hchar;	/* Subtract 1 char height     */
             clip.g_y += gl_hchar;	/* Move the Ypos down 1 level */ 
          }
      
          if( strchr( ObString( PLAST( PopPtr )), DOWN_ARROW ) )
          {
            clip.g_h -= gl_hchar;	/* Subtract 1 char height      */
          }
      
          /* Set up SOURCE rectangle */
          rc_intersect( &desk, &clip );
          rect = clip;
          rc_2xy( &clip, ( WORD *)&pxy[0] );   

          if( obj == PFIRST( PopPtr ) )
              clip.g_y += gl_hchar;
      
          if( obj == PLAST( PopPtr ) )
              clip.g_y -= gl_hchar;
          rc_intersect( &desk, &clip );
          rc_2xy( &clip, ( WORD *)&pxy[4] );   

          clip = rect;
          rc_2xy( &clip, ( WORD *)&xclip[0] );   
          vs_clip( vhandle, 1, xclip );
          graf_mouse( M_OFF, 0L );
          vro_cpyfm( vhandle, 3, pxy, ( MFDB *)&location, ( MFDB *)&location );
          graf_mouse( M_ON, 0L );
      
      	  for( i = PFIRST( PopPtr ); i <= PLAST( PopPtr ); i++ )
      	  {
             if( ObType( i ) == G_STRING )
             {
                if( i == PFIRST( PopPtr ) && strchr( ObString( PFIRST( PopPtr )), UP_ARROW ))
                {
                    ObString( i ) = &PTEXTBUFF( PopPtr )[ ( PWIDTH( PopPtr ) + 1 ) * PNUM( PopPtr ) ];
                    continue;
                }
                    
                if( i == PLAST( PopPtr ) &&  strchr( ObString( PLAST( PopPtr )), DOWN_ARROW ))
                {
                    ObString( i ) = &PTEXTBUFF( PopPtr )[ ( PWIDTH( PopPtr ) + 1 ) * ( PNUM( PopPtr ) + 1 ) ];
                    continue;
                }    
                 
  	        SetNormal( i );
  	        if( CmdState( PopPtr->CharPtr[j] ) & CHECKED )
  	            CheckObj( i );
                    
                if( CmdState( PopPtr->CharPtr[j] ) & DISABLED )
                    Disable( i );
                      
                ObString( i ) = &PTEXTBUFF( PopPtr )[ ( PWIDTH( PopPtr ) + 1 ) * j++ ];
 	     }   
          }
  
      
          if( obj == PFIRST( PopPtr ) )
          {
             rect = ObRect( obj );
             objc_offset( tree, obj, &rect.g_x, &rect.g_y );
             if( strchr( ObString( obj ), UP_ARROW ) )
                 rect.g_y += gl_hchar;
             else
             {
                 rect.g_h += gl_hchar;
                 ObString( obj ) = &PTEXTBUFF( PopPtr )[ 0 ];
                 if( !(CmdState( PopPtr->CharPtr[ 0 ] ) & DISABLED) )
                   Select( obj );
                   
                 if( CmdState( PopPtr->CharPtr[ 0 ] ) & CHECKED )
                   CheckObj( obj );
             }   
          }     
      
          if( obj == PLAST( PopPtr ) )    
          {
             rect = ObRect( obj );
             objc_offset( tree, obj, &rect.g_x, &rect.g_y );
             rect.g_y -= gl_hchar;
             if( !strchr( ObString( obj ), DOWN_ARROW ) )
             {
	       rect.g_h += gl_hchar;         
               ObString( obj ) = &PTEXTBUFF( PopPtr )[ ( PWIDTH( PopPtr ) + 1 ) * ( PNUM( PopPtr ) - 1 )];
               if( !(CmdState( PopPtr->CharPtr[ PNUM( PopPtr ) - 1 ] ) & DISABLED) )
                   Select( obj );
               if( CmdState( PopPtr->CharPtr[ PNUM( PopPtr ) - 1 ] ) & CHECKED )
                   CheckObj( obj );
             }   
          }
          Objc_draw( tree, ROOT, MAX_DEPTH, &rect ); 
       }
       else
       {
          if( ( obj == PFIRST( PopPtr ) )   &&
 	      ( !strchr( ObString( obj ), UP_ARROW )) &&
              ( !( CmdState( PopPtr->CharPtr[ 0 ] ) & DISABLED )) &&
              (  CmdFlag( PopPtr->CharPtr[0] ) == '~' )
            )
            ReturnFlag = TRUE;

          if( ( obj == PLAST( PopPtr ) )   &&
 	      ( !strchr( ObString( obj ), DOWN_ARROW ) ) &&
              ( !( CmdState( PopPtr->CharPtr[ PNUM( PopPtr ) - 1 ] ) & DISABLED )) &&
              (  CmdFlag( PopPtr->CharPtr[ PNUM( PopPtr ) - 1 ] ) == '~' )
            )
            ReturnFlag = TRUE;
       }
       
       if( DelayFlag )
       {
           Evnt_timer( CLICK_DELAY );
           DelayFlag = FALSE;
       }    

       Graf_mkstate( &mk );  
   }while( (( BSTATE == 1 ) || ( BREAL == 1 )) && ( xy_inrect( mk.x, mk.y, &xrect )));

   Evnt_button( 1, 1, 0, &mk );	   /* Make sure we have an up button...*/
   return( ReturnFlag );
}




/* Pop_Blit()
 * ====================================================================
 * Blit from screen to buffer or buffer to screen for Popup Box redraws.
 *
 * IN: long *PopPtr:	Pointer to memory buffer...
 *     GRECT *clip:	GRECT of clip blit area
 *     int   flag:	0 - blit from screen to buffer
 *                      1 - blit from buffer to screen
 *
 * OUT: returns TRUE if successful, FALSE if failed.
 */
BOOLEAN
Pop_Blit( long *PopPtr, GRECT *xclip, int flag )
{
   long     location = 0L;	/* SCREEN MFDB		*/
   int      nplanes;		/* Number of planes	*/
   unsigned long size;		/* size of malloc	*/
   int      pxy[8];		/* pxy for blit		*/
   GRECT    clip;
   MFDB     PopMFDB;		/* buffer MFDB		*/

   clip      = ( GRECT )*xclip;
   clip.g_x -= 2;		/* adjust the clip area to take care of*/
   clip.g_y -= 2;		/* the edges and shadows...	       */
   clip.g_w += 5;
   clip.g_h += 5;   
   rc_2xy( &clip, ( WORD *)&pxy[0] );
   vs_clip( vhandle, 1, pxy ); 

   vq_extnd( vhandle, 1, work_out );
   nplanes = work_out[4];

   if( !flag )					/* screen to buffer blit*/
   {
     size = (unsigned long)(((long)clip.g_w + 7L )/8L) *
            (long)clip.g_h * (long)nplanes;
     size *= 2L;       
     *PopPtr = (long )malloc( (unsigned long ) size );
   }  
   
   if( !*PopPtr )
     	return( FALSE );

   PopMFDB.fd_addr 	= (long *)*PopPtr;	/* Setup the MFDB      */
   PopMFDB.fd_w		= clip.g_w;			
   PopMFDB.fd_h		= clip.g_h;
   PopMFDB.fd_wdwidth   = ( clip.g_w + 15 ) / 16;
   PopMFDB.fd_stand 	= 0;
   PopMFDB.fd_nplanes   = nplanes;
   PopMFDB.fd_r1 	= PopMFDB.fd_r2 = PopMFDB.fd_r3 = 0;
   
   graf_mouse( M_OFF, 0L );
   if(!flag )			
   { 
     /* Screen to buffer blit */  
     rc_intersect( &desk, &clip );        
     rc_2xy( &clip, ( WORD *)&pxy[0] );
     pxy[4] = pxy[5] = 0;	       
     pxy[6] = clip.g_w - 1;
     pxy[7] = clip.g_h - 1;
     vro_cpyfm( vhandle, 3, pxy, ( MFDB *)&location, &PopMFDB );
   }
   else				
   {
     /* Buffer to screen blit */
     rc_intersect( &desk, &clip );
     pxy[0] = pxy[1] = 0;	       
     pxy[2] = clip.g_w - 1; 
     pxy[3] = clip.g_h - 1;
     rc_2xy( &clip, ( WORD *)&pxy[4] );   
     vro_cpyfm( vhandle, 3, pxy, &PopMFDB, ( MFDB *)&location );
     if( *PopPtr )
         free( (long *)*PopPtr );
     *PopPtr = 0L;    
   }
   graf_mouse( M_ON, 0L );

   return( TRUE );
}




/* UpArrowStatus()
 * =====================================================================
 */
void
UpArrowStatus( POP_PTR PopPtr )
{
  OBJECT *tree;
  GRECT  rect;
    
  tree = POBJECT( PopPtr );
    
  /* Check if we need to activate the UP ARROW or deactivate it */
  if( POFFSET( PopPtr ) )	/* display the UP ARROW */
  {
     if( !strchr( ObString( PFIRST( PopPtr )), UP_ARROW ) )
     {
          rect = ObRect( PFIRST( PopPtr ) );
          objc_offset( tree, PFIRST( PopPtr ), &rect.g_x, &rect.g_y );
          
          SetNormal( PFIRST( PopPtr ) );
          ObString( PFIRST( PopPtr ) ) = &PTEXTBUFF( PopPtr )[ ( PWIDTH( PopPtr ) + 1 ) * PNUM( PopPtr )  ];
          Objc_draw( tree, ROOT, MAX_DEPTH, &rect );
     }
  }
  else
    ObString( PFIRST( PopPtr ) ) = &PTEXTBUFF( PopPtr )[ ( PWIDTH( PopPtr ) + 1 ) * ( PNUM( PopPtr ) + 2 ) ];
}



/* DownArrowStatus()
 * =====================================================================
 */
void
DownArrowStatus( POP_PTR PopPtr )
{
  OBJECT *tree;
  GRECT  rect;
    
  tree = POBJECT( PopPtr );
  
  /* Check if we need to display the DOWN ARROW */

  /* DISPLAY the DOWN ARROW */
  if( ( POFFSET( PopPtr ) + PHEIGHT( PopPtr )) <= PNUM( PopPtr ) )
  {
      if( !strchr( ObString( PLAST( PopPtr )), DOWN_ARROW ) )
      {
        rect = ObRect( PLAST( PopPtr ) );
        objc_offset( tree, PLAST( PopPtr ), &rect.g_x, &rect.g_y );

        SetNormal( PLAST( PopPtr ) );
        ObString( PLAST( PopPtr ) ) = &PTEXTBUFF( PopPtr )[ ( PWIDTH( PopPtr ) + 1 ) * ( PNUM( PopPtr ) + 1 ) ];
        Objc_draw( tree, ROOT, MAX_DEPTH, &rect );
      }
  }
  else
    ObString( PLAST( PopPtr ) ) = &PTEXTBUFF( PopPtr )[ ( PWIDTH( PopPtr ) + 1 ) * ( PNUM( PopPtr ) + 2 ) ];
}




/* SetArrowClickDelay()
 * =====================================================================
 * Set the Click Delay before the menu starts to scroll.
 */
void
SetArrowClickDelay( long delay )
{
   CLICK_DELAY = (( delay > 0L ) ? ( delay ) : ( INIT_CLICK_DELAY ));
}




/* CHECK FOR KEYCODES...
 * =====================================================================
 * Checks the string for the keystrings below so that they may be
 * subtracted from the length of the string for calculation purposes.
 */

/* CheckForCtrl()
 * =====================================================================
 * Looks for a ^ in the string.
 * Returns a pointer to the string where it starts.
 */
char 
*CheckForCtrl( char *text )
{ 
   return( strchr( text, '^' ) );
}



/* CheckForAlt()
 * =====================================================================
 * Looks for a & in the string.
 * Returns a pointer to the string where it starts.
 */
char 
*CheckForAlt( char *text )
{
   return( strchr( text, '&' ) );
}


/* CheckForCapKey()
 * =====================================================================
 * Looks for a / in the string.
 * Returns a pointer to the string where it starts.
 */
char
*CheckForCapKey( char *text )
{
   return( strchr( text, '|' ) );
}


/* CheckForCmdKey()
 * =====================================================================
 * Looks for a $ in the string.
 * Returns pointer to the string where it starts.
 */
char
*CheckForCmdKey( char *text )
{
   return( strchr( text, '$' ) );
}


/* CheckForDisable()
 * =====================================================================
 * Looks for a @ in the string.
 * Returns TRUE if found, FALSE if not.
 */
BOOLEAN
CheckForDisable( char *text )
{
   return( ( BOOLEAN )strchr( text, '@' ) );
}


/* CheckForMenuCheck()
 * =====================================================================
 * Looks for a ! in the string.
 * Returns TRUE if found, FALSE if not.
 */
BOOLEAN
CheckForMenuCheck( char *text )
{
  return( ( BOOLEAN )strchr( text, '!' ) );
}



/* CheckForSubMenu()
 * =====================================================================
 * Looks for a ~ in the string.
 * Returns the pointer to the string where it starts.
 */
char
*CheckForSubMenu( char *text )
{
   return( strchr( text, '~' ) );
}



/* CheckForFuncKey()
 * =====================================================================
 * Looks for a # in the string.
 * Returns the pointer to the string if found.
 */
char
*CheckForFuncKey( char *text )
{
   return( strchr( text, '#' ) );
}


/* CheckForDuplicate()
 * =====================================================================
 * Looks for a * in the string.
 * Returns TRUE if found, FALSE if not.
 * The length of the string to compare should be ZERO. :-)
 * This is because this string will be the width of the menu.
 */
BOOLEAN
CheckForDuplicate( char *text )
{
   return( ( BOOLEAN )strchr( text, '*' ));
}



/* CheckForKeyCodes()
 * =====================================================================
 * Checks the text string for various keycodes and subtracts
 * an appropriate amount from the length if any are found.
 * For many of these, there should only be one of them.
 * We then ADD back in the amount of ACTUAL text usage this item
 * would take. IE: a Function Key can be F10 so 3 characters...
 * or a CmdKey can be the letters: 'DEL' 3 characters added.
 * This will also fill up the CMDCHAR structures.
 */
long
CheckForKeyCodes( POP_PTR PopPtr, char *text, int num_entry )
{
    long length;
    long num;
    
    length = strlen( text );

    if( strchr( text, '@' ))
       length -= 1L;

    if( strchr( text, '!' ))
       length -= 1L;
       
    switch( CmdFlag( PopPtr->CharPtr[ num_entry ] ) )
    {
       case '^':
       case '~':
       case '#':
       case '&': length += 1L;
       		 break;
       		 
       case '|': length += 2L;
       		 break;
       		 
       case '$': switch( CmdHotKey( PopPtr->CharPtr[ num_entry ] )  )
        	 {
	    	    case 'h': /* Home */
	    	    	      strcpy( &CmdText[0], KeyText[0] ); 
	   	    	      break;
	       		    	      
	            case 'H': /* HELP */
	    	    	      strcpy( &CmdText[0], KeyText[1] );
	    	    	      break;
	       		    	      
	    	    case 'U': /* Undo */
	    	      	      strcpy( &CmdText[0], KeyText[2] );
	    	      	      break;
	       		    	      
	    	    case 'E': /* Escape*/
	    	      	      strcpy( &CmdText[0], KeyText[3] );
	    	      	      break;
	       		    	      
	    	    case 'I': /* Insert */
	     	      	      strcpy( &CmdText[0], KeyText[4] );
	     	      	      break;
	       		    	      
	    	    case 'C': /* Clr */
	    	      	      strcpy( &CmdText[0], KeyText[5] );
	    	      	      break;
	       		    	      
	    	    case 'D': /* Delete */
	    	      	      strcpy( &CmdText[0], KeyText[6] );
	     	      	      break;

	    	    case 'T': /* TAB    */
	    	      	      strcpy( &CmdText[0], KeyText[7] );
	     	      	      break;

	            case 'e': /* ENTER  */
	    	      	      strcpy( &CmdText[0], KeyText[8] );
	     	      	      break;

	    	    case 'R': /* RETURN */	    
	    	      	      strcpy( &CmdText[0], KeyText[9] );
	     	      	      break;
	     	      
	    	    default:  CmdText[0] = '\0';
	              	      break;
		 }
		 num = strlen( &CmdText[0] );
		 num = num - 2 + 1;	/* minus the ctrl codes + 1 space */
		 length += num;
       		 break;
       		 
       case '*': length = 1L;
       		 break;
       		       
       default:  
       		 break;
    }
    return( length );
}

/* END OF KEYCODE CHECKING
 * =====================================================================
 */


/* CONTROLLING THE APPEARANCE OF ITEMS
 * =====================================================================
 */

/* SetHeight()
 * =====================================================================
 * Set the height of the Menu.( in chars ).
 */
void
SetHeight( int MenuID, int Height )
{
   POP_PTR PopPtr;
   
   PopPtr = GetMenuPtr( MenuID );
   
   if( Height > 0 )
      PHEIGHT( PopPtr ) = Height;
}



/* SetNumItems()
 * =====================================================================
 * Set the number of items in the menu. This can be used to display
 * fewer items than the InsertMenu() call created.
 */
void
SetNumItems( int MenuID, int NumItems )
{
   POP_PTR PopPtr;
  
   PopPtr = GetMenuPtr( MenuID );
   if( NumItems > 0 )
      PNUM( PopPtr ) = NumItems;
}



/* CheckItem()
 * =====================================================================
 * Places or removes a check mark at the left of a menu item.
 */
void
CheckItem( int MenuID, int item, BOOLEAN check )
{
    POP_PTR PopPtr;
    
    PopPtr = GetMenuPtr( MenuID );
    if( check )
      CmdState( PopPtr->CharPtr[ item ] ) |= CHECKED;
    else
      CmdState( PopPtr->CharPtr[ item ] ) &= ~CHECKED;
}



/* DisableItem()
 * =====================================================================
 * Disables a menu item.
 */
void
DisableItem( int MenuID, int item )
{
   POP_PTR PopPtr;
   
   PopPtr = GetMenuPtr( MenuID );
   CmdState( PopPtr->CharPtr[ item ] ) |= DISABLED;
}
 


/* EnableItem()
 * =====================================================================
 * Enables a menu item.
 */
void
EnableItem( int MenuID, int item )
{
   POP_PTR PopPtr;
   
   PopPtr = GetMenuPtr( MenuID );
   CmdState( PopPtr->CharPtr[ item ] ) &= ~DISABLED;
}
 

/* SetItemCmd()
 * =====================================================================
 * This can be used to attach a submenu to a menu item.
 * You must still call 'SetSubMenuID()' tell the popups which
 * submenu to attach. Otherwise, this is a great way to change
 * the keyboard shortcuts. Use SetItemMark() to change the
 * keyboard shortcuts AFTER setting the type with the SetItemCmd().
 */
void
SetItemCmd( int MenuID, int item, char cmd ) 
{
   POP_PTR PopPtr;
   
   PopPtr = GetMenuPtr( MenuID );
   CmdFlag( PopPtr->CharPtr[ item ] ) = cmd;
}

 
/* GetItemCmd()
 * =====================================================================
 * This can be used to determine if a submenu is attached to 
 * this menu item. It returns the current command.
 * RETURNS: 
 *           '~' == SubMenu for example
 */
char
GetItemCmd( int MenuID, int item )
{
    POP_PTR PopPtr;
 
    PopPtr = GetMenuPtr( MenuID );
    return( CmdFlag( PopPtr->CharPtr[ item ] ) );
}



/* SetSubMenuID()
 * =====================================================================
 * This can be used to change the ID of the submenu associated
 * with a menu item. Nothing happens of course, if the menu_item 
 * does not have a submenu CMD.
 */
void
SetSubMenuID( int MenuID, int item, int ID )
{
   POP_PTR PopPtr;

   PopPtr = GetMenuPtr( MenuID );
   if( GetItemCmd( MenuID, item ) == '~' )
     CmdSubMenu( PopPtr->CharPtr[ item ] ) = ID;
}


/* GetSubMenuID()
 * =====================================================================
 * This can be used to get the ID of the submenu associated
 * with the menu item. Course, the item must HAVE a submenu in
 * the first place. Use GetItemCmd to determine if there is a
 * submenu.
 * RETURN: -1 if there is no submenu.
 */
int
GetSubMenuID( int MenuID, int item )
{
   POP_PTR PopPtr;

   PopPtr = GetMenuPtr( MenuID );
   if( GetItemCmd( MenuID, item ) == '~' )
     return( CmdSubMenu( PopPtr->CharPtr[ item ] ));
   else
     return( -1 );  
}


/* SetItemMark()
 * =====================================================================
 * This can be used to change the CMDKEY of a keyboard shortcut.
 * The command type itself is set with SetItem(). All except function
 * keys and submenus.
 */
void
SetItemMark( int MenuID, int item, char key )
{
   POP_PTR PopPtr;

   PopPtr = GetMenuPtr( MenuID );
   CmdHotKey( PopPtr->CharPtr[ item ] ) = key;
}
 
 
 
/* GetItemMark() 
 * =====================================================================
 * This can be used to see what the CMDKEY shortcut is associated with
 * the menu item. All except function keys and submenus.
 */
char
GetItemMark( int MenuID, int item )
{
   POP_PTR PopPtr;

   PopPtr = GetMenuPtr( MenuID );
   return( CmdHotKey( PopPtr->CharPtr[ item ] ) );
}
 

/* SetFuncMark()
 * =====================================================================
 * This can be used to set the Functio key # that is to be
 * associated with the menu item.
 */
void
SetFuncMark( int MenuID, int item, int num )
{   
   POP_PTR PopPtr;

   PopPtr = GetMenuPtr( MenuID );
   CmdFuncKey( PopPtr->CharPtr[ item ] ) = num;
}




/* GetFuncMark()
 * =====================================================================
 * This can be used to get wot function key # is associated with
 * the menu item.
 */
int 
GetFuncMark( int MenuID, int item )
{
   POP_PTR PopPtr;

   PopPtr = GetMenuPtr( MenuID );
   return( CmdFuncKey( PopPtr->CharPtr[ item ] ) );
}



/* SetItem()
 * =====================================================================
 * Set the Menu item string.
 * 1) The string must not exceed the original string length.
 * 2) Meta characters will be printed as regular text.
 * 3) If the string is longer than the original, then
 *    the new string will be truncated.
 */
void
SetItem( int MenuID, int item, char *text )
{
   POP_PTR PopPtr;
   int     count;
   char    *txtptr;
   long    length;
   long    length2;
           
   PopPtr = GetMenuPtr( MenuID );

   txtptr = PTEXT( PopPtr );
   if( txtptr )
   {
       for( count = 0; count < item; count++ )
       {    
         length = strlen( txtptr );
         txtptr += ( length + 1 );
       }
       /* we've reached the text area that we want. */
       length = strlen( txtptr );
       strncpy( txtptr, text, length );
       length2 = strlen( txtptr );
       for( count = (int)length2; count < length; count++ )
          strcat( txtptr, " " );
   }
}



/* GetItem() 
 * =====================================================================
 * Returns the string associated with the menu item WITHOUT
 * the meta characters.
 */
char
*GetItem( int MenuID, int item )
{
   POP_PTR PopPtr;
   int     count;
   char    *txtptr;
   char    *sptr;
   char    *dptr;
   long    length;
            
   PopPtr = GetMenuPtr( MenuID );

   txtptr = PTEXT( PopPtr );
   if( txtptr )
   {
       for( count = 0; count < item; count++ )
       {    
         length = strlen( txtptr );
         txtptr += ( length + 1 );
       }
       
       /* we've reached the text area that we want. */
       length = strlen( txtptr );
       sptr = txtptr;
       dptr = &TempString[0];
       
       while( *sptr )
       {
       
    	  switch( *sptr )
    	  {
    	      case '*':
       	      case '@':
              case '!': sptr++;
       		 	break;
       		 
       	      case '^':
              case '~':
       	      case '&':
       	      case '$':
       	      case '|': sptr += 2;
 	      		break;

              case '#': sptr += 2;
              		length = strlen( sptr );
              		if( length > 1 )
              		    sptr++;
              		break;
       		       
       	      default:  *dptr++ = *sptr++;
       	                break;
    	   }
       }
       *dptr = '\0';
       return( ( char *)&TempString[0] );
   }
   return( (char *)NULL );
}



/* SetStartItem()
 * =====================================================================
 * This can be used to Set the start item on a submenu. This will
 * work of course, on the main menu too.
 */
void
SetStartItem( int MenuID, int item )
{
   POP_PTR PopPtr;
   
   PopPtr = GetMenuPtr( MenuID );
   PopUpItem( PopPtr ) = item;
}
 
 

/* GetStartItem()
 * =====================================================================
 * This can be used to get the start item in a submenu. Or at least
 * wot it currently thinks it is...
 */
int
GetStartItem( int MenuID )
{
   POP_PTR PopPtr;
   
   PopPtr = GetMenuPtr( MenuID );
   return( PopUpItem( PopPtr ) );
}
 
 
 
/* END OF APPEARANCE OF ITEMS
 * =====================================================================
 */
 

/* SUBMENU HANDLING
 * =====================================================================
 */



/* DoSubMenu()
 * =====================================================================
 * Display the submenu if necessary...
 *
 */
POP_PTR
DoSubMenu( POP_PTR PopPtr, int obj )
{
   int 	   num;
   POP_PTR SubPopPtr;
   GRECT   SubRect;   
   int     MenuID;
   OBJECT  *tree;
   int     curx;
   

   ActiveTree( POBJECT( PopPtr ) );
   SubPopPtr = ( POP_PTR )NULL;
   if( obj != -1 )
   {
     num = FindNum( PopPtr, obj );
     if( CmdFlag( PopPtr->CharPtr[ num ] ) == '~' )
     {
        SubRect   = ObRect( obj );
        objc_offset( tree, obj, &SubRect.g_x, &SubRect.g_y );
      
        MenuID    = CmdSubMenu( PopPtr->CharPtr[ num ] );
        SubPopPtr = GetMenuPtr( MenuID );
        /* Displays faster if drawn on a byte boundary.*/
        curx = SubRect.g_x + SubRect.g_w -1 - gl_wchar;
        curx = (( curx + 7 )/8)*8;
        if( !ShowSubMenu( MenuID, curx, SubRect.g_y, &SubRect ))
           SubPopPtr = ( POP_PTR )NULL;
     }
   }  
   return( SubPopPtr );
}




/* ShowSubMenu()
 * =====================================================================
 * Display the Initial Submenu 
 * RETURN: TRUE - AOK
 *         FALSE - Memory allocation error.
 */
BOOLEAN
ShowSubMenu( int MenuID, int xpos, int ypos, GRECT *rect )
{
    POP_PTR PopPtr;
    int     max_height;
           
    PopPtr = GetMenuPtr( MenuID );

    PPREV( PopPtr ) = ( POP_PTR )NULL;
        
    PXPOS( PopPtr )   = xpos;
    PYPOS( PopPtr )   = ypos;

    /* Take care of height of menu */ 
    if( PHEIGHT( PopPtr ) > PNUM( PopPtr ) )
        PHEIGHT( PopPtr ) = PNUM( PopPtr );

    /* Limit Height to Height of screen - ( 2 * gl_hchars ) */
    max_height = yres - ( 2 * gl_hchar );
    while( ( PHEIGHT( PopPtr ) * gl_hchar ) > max_height )
       PHEIGHT( PopPtr ) -= 1;

    PIX_HEIGHT( PopPtr )  = ( PHEIGHT( PopPtr ) * gl_hchar );

    /* Take care of the width of the menu here...*/
    PTEXTBUFFSIZE( PopPtr ) = CalcTextBufferSize( PopPtr );
    PIX_WIDTH( PopPtr )     = ( PWIDTH( PopPtr ) * gl_wchar );

    AdjustToScreen( PopPtr, xpos, ypos, rect, TRUE, FALSE );

    if( Pop_Blit( (long *)&PMEM( PopPtr ), &PObRect( PopPtr ), 0 ) )
    {
        if( Build_Objects( PopPtr ))
          return( TRUE );
    }    
    return( FALSE );
}




/* HideSubMenu()
 * =====================================================================
 * Hides the Submenu and free's any memory that it malloc'ed.
 */
void
HideSubMenu( POP_PTR PopPtr )
{
    Pop_Blit( (long *)&PMEM( PopPtr ), &PObRect( PopPtr ), 1 );

    if( POBJECT( PopPtr ) )
        free( POBJECT( PopPtr ) );
        
    if( PTEXTBUFF( PopPtr ) )    
        free( PTEXTBUFF( PopPtr ) );
        
    POBJECT( PopPtr )   = ( OBJECT *)NULL;
    PTEXTBUFF( PopPtr ) = ( char *)NULL;
}




/* EvntSubMenu()
 * ====================================================================
 * Submenu Handling...
 * IN: POP_PTR PopPtr
 * OUT: long      -1L   No Menu Items selected.
 *                -2L   We've rentered the submenu ( won't get to user )
 *                High Word         Low Word
 *                ---------	    --------
 *                 Menu ID            -1      Selected Disabled Item
 *		   Menu ID	    Menu Item Selected Menu Item.
 */
long
EvntSubMenu( POP_PTR PopPtr )
{
   long   result;			/* Result to return with...     */
   MRETS  mk;				/* mouse structures	  	*/
   int    oldobj;			/* old object		  	*/
   int    done = FALSE;			/* Done flag to return..	*/
   int    obj;				/* object mouse is over...      */
   int    event;			/* Mouse button event flag	*/
   					/* TRUE = event occurred	*/
   POP_PTR SubPopPtr;			/* Pointer to Submenu...        */
   POP_PTR TempPtr;			/* Temporary Submenu Pointer    */
   
   GRECT  SubRect;			/* ObRect of Submenu		*/
   GRECT  TempRect;			/* Temp ObRect		        */
   
   GRECT  wall;   			/* ObRect for boundary checks   */
   GRECT  BoxRect;			/* ObRect for boundary checks   */
   GRECT  DragRect;			/* ObRect for Drag boundary     */

   OBJECT *tree;			/* Object tree declaration      */
   int    Action;			/* Action that is occurring     */
   int    num;				/* temp number storage		*/
   int    ActiveObj;			/* Current Active Object        */
   
   long CurTimeHz;			/* Time in 200Hz...*/
   BOOLEAN MenuDelayFlag;		/* Display Submenu Delay in effect*/
   BOOLEAN MenuDragFlag;		/* Drag Submenu Delay in effect  */
   BOOLEAN DragStart;			/* Start Drag Submenu Delay...   */
   int  MenuObject;			/* Object to start the timer on*/            
   int  OldY;				/* Old Y pos for a drag */   
   int  OldX;
         
   ActiveTree( POBJECT( PopPtr ) );
   Graf_mkstate( &mk );

   oldobj     = -1;
   obj        = objc_find( tree, ROOT, MAX_DEPTH, mk.x, mk.y );
   SubPopPtr  = ( POP_PTR )NULL;
   BoxRect    = ObRect( ROOT );
   result     = -1L;
   OldY	      = 0;
   OldX       = 0;
      
   MenuDelayFlag = FALSE;	
   MenuDragFlag  = FALSE;      
   DragStart     = FALSE;
   ActiveObj     = -1;
   BSTATE 	 = BREAL = 0;

   do
   {
       if( obj != -1 )
       {	
	   if( obj != oldobj )
   	   {
	     wall = ObRect( obj );
	     objc_offset( tree, obj, &wall.g_x, &wall.g_y );
 	     if( SubPopPtr )
   	     {
	        PPREV( SubPopPtr ) = ( POP_PTR )NULL;
	        HideSubMenu( SubPopPtr );
	        SubPopPtr = ( POP_PTR )NULL;
	        MenuDragFlag  = FALSE;
	        MenuDelayFlag = FALSE;
	        DragStart     = FALSE;
	        ActiveObj = -1;
	     }
	     if(( oldobj != -1 ) && ( oldobj != obj ))
	          deselect( tree, oldobj );

	     if( ( obj == PFIRST( PopPtr ) && strchr( ObString( obj ), UP_ARROW ) ) ||
	         ( obj == PLAST( PopPtr ) && strchr( ObString( obj ), DOWN_ARROW ) )
	       )
	       deselect( tree, obj );  
	     else
	     {
	        if( !IsDisabled( obj ) ) 
	        {
   	           select( tree, obj );

	           if( ( num = FindNum( PopPtr, obj )) != -1 )
		   {
		      if( CmdFlag( PopPtr->CharPtr[ num ] ) == '~' )
		      {
		         Supexec( GetTimeHz );
		         CurTimeHz     = TimeInHz;
		         MenuDelayFlag = TRUE;
		         MenuDragFlag  = FALSE;
	                 DragStart     = FALSE;
		         MenuObject    = obj;
		      }  
		   }	            
   	        }  
   	     }  
	   }
       }
       else
       {
	 if(( oldobj != -1 ) && ( oldobj != obj ) )
         {
	    deselect( tree, oldobj );
         }
       }
       oldobj = obj;
       
       Graf_mkstate( &mk );
       OldY = mk.y;
       OldX = mk.x;
       do
       {
       	    BSTATE = 0;
            if( MenuDragFlag )
            {
               if( ( !xy_inrect( mk.x, mk.y, &wall ) || 
                   ( SubPopPtr && xy_inrect( mk.x, mk.y, &SubRect ) ))
                 )
               {
		  if( DragStart )
		  {
                    Supexec( GetTimeHz );
                    CurTimeHz = TimeInHz;
                    DragStart = FALSE;
                    DragRect.g_x = mk.x;
                    DragRect.g_y = SubRect.g_y;
                    DragRect.g_h = SubRect.g_h;
                    
                    OldX = mk.x;
                    OldY = mk.y;
 		                       
                    if( mk.x <= SubRect.g_x )
                    {
                       DragRect.g_w = SubRect.g_w + ( SubRect.g_x - DragRect.g_x );
                    }
                    else
                    {
		       DragRect.g_x = SubRect.g_x;
                       DragRect.g_w = mk.x - SubRect.g_x + 1;
                    }
                  }  

	          if( SubPopPtr )
                  {
                       if( !DragStart )
                       {
		         Supexec( GetTimeHz );
		         if( ( TimeInHz - CurTimeHz ) >= SUBDRAG_DELAY )
		             MenuDragFlag = FALSE;

		         if( !xy_inrect( mk.x, mk.y, &DragRect ) ||
		             (xy_inrect( mk.x, mk.y, &DragRect ) &&
		              ( mk.x == OldX ) &&
		              ( mk.y != OldY )
			     ) 
		           )  
		           MenuDragFlag = FALSE;    
		         else
		         {
	                    DragRect.g_x = mk.x;
        	            if( mk.x <= SubRect.g_x )
                	    {
                       	       DragRect.g_w = SubRect.g_w + ( SubRect.g_x - DragRect.g_x );
                    	    }
                    	    else
                   	    {
		               DragRect.g_x = SubRect.g_x;
                               DragRect.g_w = mk.x - SubRect.g_x + 1;
                            }
		         }    
		       }      

                       /* We're in the new submenu...*/
                       if( xy_inrect( mk.x, mk.y, &SubRect ) )
                       {
                         Action = 0;
                         MenuDragFlag = FALSE;
                         break;    
                       }    
                       MenuDelayFlag = FALSE;
                  }
               }
            }
            else
	    {            
            	BoxRect = ObRect( ROOT );
            	if( xy_inrect( mk.x, mk.y, &BoxRect ) )
            	{
               	    /* We're in our current submenu still
                     * but we might be in a new menu item.
                     */
               	    Action = 1;
               	    break;
                }      
                else           
                {
                    /* Checking to see if we're in any of the previous menus */
                    TempPtr = PPREV( PopPtr );
                    while( TempPtr )
                    {
                       /* We're in a previous submenu or root */
                       TempRect = PObRect( TempPtr );
                       if( xy_inrect( mk.x, mk.y, &TempRect ) )
                       {
                          Action = 2;
                          goto abort;
	               }
	               TempPtr = PPREV( TempPtr );
                    }
                }
                
                /* if we reach here, we're outside of all the rectangles.*/
	        if( SubPopPtr )
	        {
                   HideSubMenu( SubPopPtr );
	           PPREV( SubPopPtr ) = ( POP_PTR )NULL;
	           SubPopPtr     = ( POP_PTR )NULL;
	           ActiveObj     = -1;
	           MenuDragFlag  = FALSE;
	           MenuDelayFlag = FALSE;
                }

	        if( oldobj != -1 )
	        {
	          deselect( tree, oldobj );
	          oldobj = obj = -1;
	        }  
            }
            Action = -1;

            OldX = mk.x;
            OldY = mk.y;
            Evnt_timer( 0L );	     /* Kludge to put in a delay 
            			      * so that we can get a new mouse
            			      * x,y with > 1 pixel difference
            			      */
            Graf_mkstate( &mk );
	}while( !BSTATE );
abort:
       /* we've exited due to a button click, OR
        * we've entered one of the rectangles.
        * Let's see wot we've got.
        * If Action == -1, then the user clicked outside of
        * ANY rectangles and we should just exit and close
        * down all of the menus.
        */
       Graf_mkstate( &mk );
       obj = objc_find( tree, ROOT, MAX_DEPTH, mk.x, mk.y );
 
       /* Fix for when the submenu is displayed and we click
        * on its title to exit.
        */
       if(  xy_inrect( mk.x, mk.y, &wall ) &&
            SubPopPtr && MenuDragFlag && ( BSTATE == 1 ) )
	    Action = 1;            
          
       event = FALSE;
       switch( Action )
       {
          case 0:  /* Entered the New Submenu, if there is one */
	           if( SubPopPtr )
        	   {
        	      /* need to check wot is returned...*/
        	      result = EvntSubMenu( SubPopPtr );
	              if(( result == -1L ) || ( result != -2L ) )
	              {
	                 obj = -1;
	                 event = TRUE;
	                 goto leave;
	              }
	              TempPtr = SubPopPtr;   
	              PPREV( SubPopPtr ) = ( POP_PTR )NULL;
	              HideSubMenu( SubPopPtr );
	              SubPopPtr     = ( POP_PTR )NULL;
	              MenuDragFlag  = FALSE;
		      MenuDelayFlag = FALSE;

		      Graf_mkstate( &mk );
                      obj = objc_find( tree, ROOT, MAX_DEPTH, mk.x, mk.y );
        	      num = FindNum( PopPtr, obj );
       		      if( ( obj != -1 ) && ( ActiveObj == obj ) &&
       		          ( CmdFlag( PopPtr->CharPtr[ num ] ) == '~' ) &&
       		          ( GetMenuPtr( CmdSubMenu( PopPtr->CharPtr[ num ] )) == TempPtr )
       		        )
		      {
  		        if( !IsSelected( obj ) )
			    select( tree, obj );

  		        if( ( SubPopPtr = DoSubMenu( PopPtr, obj )) != ( POP_PTR )NULL )
                        {
                          ActiveObj = obj;
	                  SubRect = PObRect( SubPopPtr );
			  ActiveTree( POBJECT( PopPtr ) );
	                  PPREV( SubPopPtr ) = PopPtr;
                          MenuDragFlag       = TRUE;
                          DragStart          = TRUE;
 		          wall = ObRect( obj );
		          objc_offset( tree, obj, &wall.g_x, &wall.g_y );
	                }  
		      }
		      else
		        ActiveObj = -1;
	           }     
          	   break;
           	   
          case 1:  /* we are still in the current submenu...*/
                   /* The user click on an arrow button? */
       		   if( (( BSTATE == 1 ) || ( BREAL == 1 )) && (( obj == PFIRST( PopPtr)  && strchr( ObString( obj ), UP_ARROW )) ||
                       (( obj == PLAST( PopPtr ) ) && strchr( ObString( obj ), DOWN_ARROW ))
                     )) 
                   {
          	      if( Pop_Arrow( PopPtr, obj ) )
                      {
		         if( ( num = FindNum( PopPtr, obj )) != -1 )
		         {
		           if( CmdFlag( PopPtr->CharPtr[ num ] ) == '~' )
		           {
		             Supexec( GetTimeHz );
		             CurTimeHz     = TimeInHz;
		             MenuDelayFlag = TRUE;
		             MenuObject    = obj;
		           }  
		         }	            
                      }
       		   }
       		   else
       		   {
       		      if( !SubPopPtr && MenuDelayFlag && ( MenuObject == obj ))
       		      {
       		        Supexec( GetTimeHz );
       		        if( ( TimeInHz - CurTimeHz ) >= SUBMENU_DELAY )
       		        {
       		             MenuDelayFlag = FALSE;
			     if( !IsSelected( obj ) )
			          select( tree, obj );

 		             if( ( SubPopPtr = DoSubMenu( PopPtr, obj )) != ( POP_PTR )NULL )
		             {
		               ActiveObj = obj;
	                       SubRect   = PObRect( SubPopPtr );
			       ActiveTree( POBJECT( PopPtr ) );
	                       PPREV( SubPopPtr ) = PopPtr;
	                       MenuDragFlag       = TRUE;
	                       DragStart          = TRUE;
		               wall = ObRect( obj );
		               objc_offset( tree, obj, &wall.g_x, &wall.g_y );
	                     }     
      		             
       		        }    
       		      }
       		      else
       		        MenuDelayFlag = FALSE;
       		            
       		      /* we are just in another menu item.
       		       * If a submenu was displayed, hide it.
       		       */
		      if( SubPopPtr && ( ActiveObj != obj ) )
		      {
	                PPREV( SubPopPtr ) = ( POP_PTR )NULL;
                        HideSubMenu( SubPopPtr );
	                SubPopPtr     = ( POP_PTR )NULL;
		        MenuDragFlag  = FALSE;
		        MenuDelayFlag = FALSE;
	                ActiveObj     = -1;
	              }
	              
	              /* if the buttons are down, they clicked on something*/
	              if(( BSTATE == 1 ) || ( BREAL == 1 ))
	              {
	                  event = TRUE;
	                  
                          obj = objc_find( tree, ROOT, MAX_DEPTH, mk.x, mk.y );
                          
                          CurMenu   = PMENUID( PopPtr );
                          CurObject = obj;
                          result    = 0L;
	              }
       		   }
          	   break;
          	   
          case 2:  /* we have entered a previous submenu...  */
		   result = -2L;

          case -1: /* we have clicked outside of ANY submenus.*/
          	   if( Action == -1 )
          	   {
          	      /* don't exit if not a left button click */
          	      if( BSTATE != 1 )
          	        goto leave;
          	   }
          	   CurMenu = CurObject = -1;
          default: event = TRUE;
		   obj    = -1;
          	   break;
       }

leave:              
       if( event )
       {
          if( SubPopPtr )
          {
             PPREV( SubPopPtr ) = ( POP_PTR )NULL;
             HideSubMenu( SubPopPtr );
      	     SubPopPtr = ( POP_PTR )NULL;
          }
	  done = TRUE;
       }	 

   }while( !done );

         
   if(( obj != -1 ) && ( result != -2L ) && ( result != -1L ) )
   {
      result = 0L;
      result = ( int )PMENUID( PopPtr );
      result = ( result << 16L );
      
      if( !IsDisabled( obj ) )
        obj = FindNum( PopPtr, obj );
      else
        obj = -1;
      result |= ( obj & 0x0000FFFF );
   }
   return( result );
}



/* FindNum()
 * =====================================================================
 * Given the object #, find the adjusted index of the menu item.
 */
int
FindNum( POP_PTR PopPtr, int obj )
{
    int offset;
    
    offset = POFFSET( PopPtr );
    if( offset > 0 )
        offset -= 1;
    obj -= PFIRST( PopPtr );
    obj += offset;    
    return( obj );    
}




/* MenuChoice()
 * =====================================================================
 * Returns the CURRENT values of the menu and object clicked on.
 * Call ONLY after the PopMenuSelect() call has returned.
 */
long
MenuChoice( void )
{
   long value;
   POP_PTR PopPtr;
   int obj;
   
   value = 0L;
   value = ( int )CurMenu;
   value = ( value << 16L );
   
   if( CurMenu != -1 )
   {
      PopPtr = GetMenuPtr( CurMenu );   
      obj = FindNum( PopPtr, CurObject );
   }     
   else
      obj = -1;
   value |= ( obj & 0x0000FFFF );
   return( value );
}


/* END OF SUBMENU ROUTINES
 * =====================================================================
 */
 




/* SUBMENU DELAY ROUTINES
 * =====================================================================
 */

/* GetTimeHz()
 * =====================================================================
 * Gets the System time in 20ms ticks.
 */
long
GetTimeHz( void )
{
   long *ptr;
   
   ptr = ( long *)0x4BA;
   TimeInHz = *ptr;
   return( TimeInHz );
}




/* SetSubMenuDelay()
 * =====================================================================
 * Sets the length of time before a submenu appears as the user drags
 * the pointer thru a menu list.
 * The Units are in # of Milliseconds. ( 1000 = 1 second ).
 */
void
SetSubMenuDelay( long ms )
{
   if( ms < 0L )
       ms = INIT_DISPLAY_DELAY;
   if( ms )    
       ms /= 20L;     
   SUBMENU_DELAY = ms;
}




/* SetSubDragDelay()
 * =====================================================================
 * Sets the length of time the user has to drag diagonally, a pointer
 * to an active submenu before the submenu closes.
 */
void
SetSubDragDelay( long ms )
{
   if( ms < 0L )
       ms = INIT_DRAG_DELAY;
   if( ms )    
       ms /= 20L;
   SUBDRAG_DELAY = ms;
}


/* END OF SUBMENU DELAY ROUTINES
 * =====================================================================
 */



/* AdjustToScreen()
 * =====================================================================
 * Takes the Submenu XY and adjusts it so that it fits within
 * the screen area still. If there is an optional object, we will
 * adjust around that.
 * If we DO adjust, we'll try to get it on a byte boundary.
 * BOOLEAN byte_flag - a flag to align the xpos on a byte boundary.
 *                   - TRUE - YES!
 *		     - NOTE: if the menu does not want byte adjusting
 *			     BUT it is off the screen, we will adjust
 *			     it back ONTO the screen on a byte boundary.
 *			     We just won't byte_adjust it if its already
 *			     on the screen in its entirety.
 * BOOLEAN Display_Flag; - a flag for vertical alignment of submenus.
 *			 - When the submenu extends BELOW the bottom
 *			   of the screen, there are 2 ways to adjust
 *			   the menu.
 *			 1)Adjust by displaying the menu upwards vertically
 *			   from the rectangle button.
 *			 2)Adjust by moving the menu upwards 1 gl_hchar
 *			   at a time until the menu does not extend
 *			   below the screen boundary.
 *			 TRUE - Use Version 1
 *			 FALSE - Use Version 2
 */
void
AdjustToScreen( POP_PTR PopPtr, int xpos, int ypos, GRECT *rect, BOOLEAN byte_flag, BOOLEAN Display_Flag )
{
    int temp;
    int position;
           
    /* Handle the X Coordinate Adjustments...
     *------------------------------------------------------------------
     */
    temp = xpos + PIX_WIDTH( PopPtr );
    if( temp > ( xres - 4 ))
    {
        if( !byte_flag )
        {
          temp -= ( xres - 4 );
          temp = ( temp + gl_wchar )/gl_wchar;
          temp *= gl_wchar;
          xpos -= temp;
        }
        else
        {
          xpos = rect->g_x - PIX_WIDTH( PopPtr );
          xpos = ( xpos + gl_wchar )/gl_wchar;
          xpos *= gl_wchar;
        }        
    }
    else
    {
        if( byte_flag )
        {
          xpos = ( xpos + ( gl_wchar - 1 ) )/gl_wchar;
          xpos *= gl_wchar;
        }     
    }
    
    /* Handle X limits < 0 */
    while( xpos <= 0 )
       xpos += gl_wchar;
    PXPOS( PopPtr )   = xpos;
    

    /* Handle the Y Coordinate Adjustments...
     *------------------------------------------------------------------
     */
    /* Calculates the Position so that the submenu start item 
     * is adjacent to the button that activated it.
     */
    position = ( PopUpItem( PopPtr ) + ( PNUM( PopPtr ) > PHEIGHT( PopPtr ) )) / PHEIGHT( PopPtr );
    if( position )
    {
       position = PopUpItem( PopPtr );
       temp = PNUM( PopPtr ) - PHEIGHT( PopPtr ) + 1;

       if( position  >= temp )
           position   = temp;
       position -= 1;    
    }      
    position = PopUpItem( PopPtr ) - position;
    ypos = ypos - ( position * gl_hchar );

    
    /* Calculates a new position if the submenu exceeds the
     * height of the screen.
     */        
    temp = ypos + PIX_HEIGHT( PopPtr );
    if( temp > ( yres - 4 ))
    {
       if( Display_Flag )
          ypos = ypos - ( (PHEIGHT( PopPtr ) - position - 1 )* gl_hchar );        
       else
       {
          do
          {
             ypos -= gl_hchar;
             temp = ypos + PIX_HEIGHT( PopPtr );
          }while( temp > ( yres - 4 ) );
       }
    }
    
    while( ypos <= ( gl_hchar + ( gl_hchar/2 ) ))
       ypos += gl_hchar;
       
    PYPOS( PopPtr )   = ypos;
}




/* WaitForUpButton()
 * =====================================================================
 * Waits for an up button.
 */
void
WaitForUpButton( void )
{
  MRETS mk;
  
  do
  {
     Graf_mkstate( &mk );
  }while( mk.buttons );
}


