/*
 *  FILE: menustuff.c
 *  Support routines for dealing with menus and menuitems.
 *
 *  NOTE: these routines should only be used on a menustrip that is
 *  not attached to a window or guaranteed not to be actively used by
 *  Intuition (eg. by using the MENUVERIFY flag).
 *
 *  Public Domain, but keep my name in it as the original author.
 *  31-Oct-88	Jan Sven Trabandt   added to gimme.lib
 */


#define I_AM_MENUSTUFF
#include "gimmelib/gimmefuncs.h"
#include "gimmelib/menustuff.h"


/* internal flags: must stay within defined GMI_RESERVED bits */
#define GMI_SUBITEM	(1L << 30)      /* process subitem, not item */
#define GMI_DO_TOP	(1L << 29)      /* modify top edge */
#define GMI_DO_ME	(1L << 28)      /* modify mutual exclude */
#define GMI_SNEAKY	(1L << 27)      /* sneaky -- don't modify too much */

#define GMI_ADJUST	(GMI_FIXUP | GMI_SHUFFLE)
#define GMI_ADJUSTME	(GMI_FIXUPME | GMI_SHUFFLEME)


ULONG addMenuItem( menu, item, positem, possub, numitem, myflags )
    struct Menu     *menu;
    struct MenuItem *item;
    SHORT	    positem, possub, numitem;
    ULONG	    myflags;
{
    register struct MenuItem	*temp, **old;
    struct MenuItem		*save, *savelast;
    SHORT			pos, sub;
    SHORT			top, topedge;

    if( !menu || !item || !numitem ) {
	return( MENUNULL );
    }
    if( myflags & GMI_SUBITEM ) {
	old = &((struct MenuItem *)menu)->SubItem;
    } else {
	old = &menu->FirstItem;
    }

    if( myflags & GMI_ADDSUBITEM ) {
	if( !*old ) {                   /* if no items, can't put subitem */
	    return( MENUNULL );
	}
    } else {
	/* find last item in list to be added and truncate list appropriately */
	for( temp = item; temp->NextItem; temp = temp->NextItem ) {
	    if( --numitem == 0 ) {
		temp->NextItem = NULL;
		break;
	    }
	} /* for */
	savelast = temp;	    /* save pointer to last item to be added */

	if( positem == 0 || !*old ) {       /* add as first (sub)item */
	    savelast->NextItem = *old;
	    *old = item;
	    if( *old && (myflags & (GMI_ADJUST|GMI_ADJUSTME|GMI_APPEAREND)) ) {
		pos = 0;
		save = item;
		myflags |= GMI_SNEAKY;		/* careful handling */
		goto calculate_adjustment_mi;
	    }
	    adjustMenuItems( item, savelast->NextItem, 0, 0, myflags );
	    return( SHIFTITEM(0) | SHIFTSUB(NOSUB) );
	}
    }

    /* find the item preceding where to insert, and position# for new item */
    pos = 1;
    for( temp = *old; temp->NextItem; temp = temp->NextItem ) {
	if( --positem == 0 ) {
	    break;
	}
	++pos;
    } /* for */

    if( myflags & GMI_ADDSUBITEM ) {
	sub = ITEMNUM( addMenuItem(temp, item, possub, 0, numitem,
				(myflags | GMI_SUBITEM) & ~GMI_ADDSUBITEM) );
    } else {
	savelast->NextItem = temp->NextItem;
	save = temp;		    /* save ptr to item after which to add */
	if( myflags & (GMI_ADJUST | GMI_ADJUSTME) ) {
calculate_adjustment_mi: {}
	    /* calculate top edge for new item */
	    topedge = 0;
	    for( temp = *old; temp; temp = temp->NextItem ) {
		top = temp->TopEdge + temp->Height;
		if( temp == save ) {
		    if( !(myflags & GMI_SNEAKY) ) {
			save->NextItem = item;
			if( top > topedge ) {
			    topedge = top;
			}
		    }
		    if( !(myflags & GMI_APPEAREND) ) {
			break;			/* exit for loop */
		    }
		    temp = savelast;	    /* skip over what we added */
		}
		if( top > topedge ) {
		    topedge = top;
		}
	    } /* for */
	    adjustMenuItems( item, savelast->NextItem, topedge, pos, myflags );
	} else {
	    save->NextItem = item;
	}
	sub = NOSUB;
    }

    return( SHIFTITEM(pos) | SHIFTSUB(sub) );
} /* addMenuItem */


ULONG addMenu( menuptr, menu, posmenu, nummenu, myflags )
    struct Menu     **menuptr;
    struct Menu     *menu;
    SHORT	    posmenu, nummenu;
    ULONG	    myflags;
{
    register struct Menu    *temp, **old;
    struct Menu 	    *save, *savelast;
    SHORT		    pos;
    SHORT		    left, leftedge;

    if( !nummenu || !menuptr || !menu ) {
	return( MENUNULL );
    }
    old = menuptr;

    /* find last menu in list to be added and truncate list appropriately */
    for( temp = menu; temp->NextMenu; temp = temp->NextMenu ) {
	if( --nummenu == 0 ) {
	    temp->NextMenu = NULL;
	    break;
	}
    } /* for */
    savelast = temp;		/* save pointer to last menu to be added */

    if( posmenu == 0 || !*old ) {       /* add to front of menu strip */
	savelast->NextMenu = *old;
	*old = menu;
	if( *old && (myflags & (GMI_ADJUST | GMI_APPEAREND)) ) {
	    pos = 0;
	    save = menu;
	    myflags |= GMI_SNEAKY;		/* careful handling */
	    goto calculate_adjustment_m;
	}
	adjustMenuLefts( menu, savelast->NextMenu, 0, myflags );
	return( SHIFTMENU(0) | SHIFTITEM(NOITEM) | SHIFTSUB(NOSUB) );
    }

    /* find the menu preceding where to insert, and position# for new menu */
    pos = 1;
    for( temp = *old; temp->NextMenu; temp = temp->NextMenu ) {
	if( --posmenu == 0 ) {
	    break;
	}
	++pos;
    } /* for */

    savelast->NextMenu = temp->NextMenu;
    save = temp;		/* save ptr to menu after which to add */
    if( myflags & (GMI_ADJUST | GMI_ADJUSTME) ) {
calculate_adjustment_m: {}
	/* calculate left edge for new menu */
	leftedge = 0;
	for( temp = *old; temp; temp = temp->NextMenu ) {
	    left = temp->LeftEdge + temp->Width;
	    if( temp == save ) {
		if( !(myflags & GMI_SNEAKY) ) {
		    save->NextMenu = menu;
		    if( left > leftedge ) {
			leftedge = left;
		    }
		}
		if( !(myflags & GMI_APPEAREND) ) {
		    break;		    /* exit for loop */
		}
		temp = savelast;	/* skip over what we added */
	    }
	    if( left > leftedge ) {
		leftedge = left;
	    }
	} /* for */
	adjustMenuLefts( menu, savelast->NextMenu, leftedge, myflags );
    } else {
	save->NextMenu = menu;
    }

    return( SHIFTMENU(pos) | SHIFTITEM(NOITEM) | SHIFTSUB(NOSUB) );
} /* addMenu */


struct MenuItem *removeMenuItem( menu, item, positem, possub, numitem, myflags )
    struct Menu     *menu;
    struct MenuItem *item;
    SHORT	    positem, possub, numitem;
    ULONG	    myflags;
{
    register struct MenuItem	*temp, **old;
    struct MenuItem		*save;
    SHORT			pos;	    /* position for mutual exclude */

    if( !menu ) {
	return( NULL );
    }
    if( myflags & GMI_SUBITEM ) {
	old = &((struct MenuItem *)menu)->SubItem;
    } else {
	old = &menu->FirstItem;
    }
    if( !*old || !numitem ) {           /* if no menu items */
	return( NULL );
    }
    if( myflags & GMI_REMSUBITEM ) {
	save = item;		    /* don't want to check item address yet */
	item = NULL;
    } else {
	if( item ) {
	    positem = -1;
	}
    }
    pos = 0;
    temp = *old;
    if( temp != item && positem != 0 && temp->NextItem ) {
	/* find the "item" preceding where to remove */
	for( ; temp->NextItem && --positem != 0; ) {
	    ++pos;
	    if( temp->NextItem == item ) {      /* found the item */
		break;
	    }
	    temp = temp->NextItem;	    /* next in loop */
	    if( !temp->NextItem ) {         /* no more to follow so stop */
		break;
	    }
	} /* for */
	old = &temp->NextItem;
    }
    /* now old points to a MenuItem pointer to item to be removed */
    if( item && *old != item ) {            /* if its not the right item */
	return( NULL );
    }

    if( myflags & GMI_REMSUBITEM ) {
	item = removeMenuItem( temp, save, possub, 0, numitem,
				(myflags | GMI_SUBITEM) & ~GMI_REMSUBITEM );
    } else {
	if( !item ) {
	    item = *old;
	}
	if( myflags & GMI_ADDRONLY ) {
	    return( item );
	}
	/* find last one to be removed */
	for( temp = item; temp->NextItem; temp = temp->NextItem ) {
	    if( --numitem == 0 ) {
		break;
	    }
	} /* for */
	*old = temp->NextItem;
	temp->NextItem = NULL;
	myflags &= ~(GMI_FIXUP | GMI_FIXUPME);
	if( myflags & GMI_SHUFFLE ) {
	    myflags |= GMI_FIXUP;
	}
	if( myflags & GMI_SHUFFLEME ) {
	    myflags |= GMI_FIXUPME;
	}
	adjustMenuItems( *old, NULL, item->TopEdge, pos, myflags );
    }

    return( item );
} /* removeMenuItem */


struct Menu *removeMenu( menuptr, menu, posmenu, nummenu, myflags )
    struct Menu     **menuptr;
    struct Menu     *menu;
    SHORT	    posmenu, nummenu;
    ULONG	    myflags;
{
    register struct Menu    *temp, **old;

    if( !nummenu || !menuptr || !*menuptr ) {
	return( NULL );
    }
    old = menuptr;
    if( menu ) {
	posmenu = -1;
    }
    temp = *old;
    if( temp != menu && posmenu != 0 && temp->NextMenu ) {
	/* find the menu preceding where to remove */
	for( ; temp->NextMenu && --posmenu != 0; ) {
	    if( temp->NextMenu == menu ) {      /* found the menu */
		break;
	    }
	    temp = temp->NextMenu;	    /* next in loop */
	    if( !temp->NextMenu ) {         /* no more to follow so stop */
		break;
	    }
	} /* for */
	old = &temp->NextMenu;
    }
    /* now old points to a Menu pointer to menu to be removed */
    if( menu && *old != menu ) {            /* if its not the right menu */
	return( NULL );
    }

    if( !menu ) {
	menu = *old;
    }
    if( myflags & GMI_ADDRONLY ) {
	return( menu );
    }
    /* find last one to be removed */
    for( temp = menu; temp->NextMenu; temp = temp->NextMenu ) {
	if( --nummenu == 0 ) {
	    break;
	}
    } /* for */
    *old = temp->NextMenu;
    temp->NextMenu = NULL;
    adjustMenuLefts( *old, NULL, menu->LeftEdge, myflags );

    return( menu );
} /* removeMenu */


short adjustMenuItems( item, stop, topedge, meposition, myflags )
    struct MenuItem *item, *stop;
    SHORT	    topedge, meposition;
    register ULONG  myflags;
{
    register struct MenuItem	*temp;

    if( !(myflags & (GMI_ADJUST | GMI_ADJUSTME)) ) {
	return( -1 );
    }
    if( myflags & GMI_FIXUP ) {
	myflags |= GMI_DO_TOP;
    }
    if( myflags & GMI_FIXUPME ) {
	myflags |= GMI_DO_ME;
    }
    for( temp = item; temp; temp = temp->NextItem ) {
	if( temp == stop ) {
	    if( !(myflags & (GMI_SHUFFLE | GMI_SHUFFLEME)) ) {
		break;
	    }
	    myflags &= ~(GMI_DO_TOP | GMI_DO_ME);
	    if( (myflags & GMI_SHUFFLE) && !(myflags & GMI_APPEAREND) ) {
		myflags |= GMI_DO_TOP;
	    }
	    if( myflags & GMI_SHUFFLEME ) {
		myflags |= GMI_DO_ME;
	    }
	}
	if( myflags & GMI_DO_TOP ) {
	    temp->TopEdge = topedge;
	}
	topedge += temp->Height;
	if( myflags & GMI_DO_ME ) {
	    temp->MutualExclude = ~(1L << meposition);
	}
	++meposition;
    } /* for */
    return( 0 );
} /* adjustMenuItems */


short adjustMenuLefts( menu, stop, leftedge, myflags )
    struct Menu     *menu, *stop;
    register SHORT  leftedge;
    ULONG	    myflags;
{
    register struct Menu    *temp;

    if( !(myflags & (GMI_ADJUST)) ) {
	return( -1 );
    }
    for( temp = menu; temp; temp = temp->NextMenu ) {
	if( !(myflags & GMI_SHUFFLE) && temp == stop ) {
	    break;
	}
	temp->LeftEdge = leftedge;
	leftedge += temp->Width;
    } /* for */
    return( 0 );
} /* adjustMenuLefts */
