//#ident "$Id:$"

#import "PopUpMenu.h"

#import <appkit/Panel.h>
#import <appkit/Window.h>
#import <appkit/Box.h>
#import <appkit/View.h>
#import <appkit/Matrix.h>
#import <appkit/Cell.h>
#import <appkit/MenuCell.h>
#import <appkit/Text.h>
#import <appkit/TextFieldCell.h>
#import <appkit/Application.h>
#import <appkit/Font.h>
#import <appkit/graphics.h>
#import <dpsclient/psops.h>

#import <ctype.h>
#import <strings.h>
#import <stdlib.h>


@implementation PopUpMenu


#define IS_WHITESPACE(x) (x==' ' || x=='\n' || x=='\t'  || x!='\0')
#define REMOVE_LEADING_WHITE(x) do { while(IS_WHITESPACE(**x)) (*(x))++; } while(0)
#define OPEND '['  // the delimiters
#define CLOSED ']'
#define SEPARATOR '/'


/* menu string looks like

	"[[LastMenuItem/action/tag]
	 [MenuItem9/action/tag]
	 [MenuItem8 [
		"SubMenuItem3/action/tag
		"SubmenuItem2/action/tag
		"SubMenuItem1/action/tag]]
	 [MenuItem1/action/tag]]"
	
 */
- initWithMenuString: (char*)string
{
	NXRect rect;
	id proto, box;
	
	// every menu cell will appear as the following....
	proto = [[MenuCell alloc] init];
	[proto setBezeled: NO];
	[proto setBordered: NO];
	[proto setAlignment:NX_CENTERED];
	[proto setIconPosition: NX_ICONRIGHT];
	[proto setFont: [Font newFont: "Times-Roman" size: 12]];
	
	rect.origin.x = rect.origin.y = 0.0;
	rect.size.width = 100.0; rect.size.height=300.0;

	matrix = [[Matrix alloc] initFrame:&rect mode:NX_HIGHLIGHTMODE
		prototype:proto numRows:0 numCols:1];
	[matrix setAutosizeCells:YES];
	[matrix setFlipped:NO];
	[matrix setBackgroundGray: NX_WHITE];
	[matrix setCellBackgroundGray: NX_WHITE];
	[super initContent:&rect style:NX_PLAINSTYLE backing:NX_BUFFERED 
		buttonMask: 0 defer: NO];
	[self setFloatingPanel:YES];
	[self setBecomeKeyOnlyIfNeeded:NO];
	[self makeFirstResponder:self];
	supermenu=nil; // top of the menu chainq
	inited = NO;
	
	// create a box as the content view, this allows us to have a simple
	// black line for the border of the window.  The box view (now the 
	// content view) will only have one subview, a matrix of menucells
	box = boxView = [[Box alloc] init];
	[box setTitlePosition: NX_NOTITLE];
	[box setBorderType: NX_LINE];
	[box setOffsets:0.0 :0.0];
	[super setContentView: box];
	//[contentView addSubview: box];
	[box setContentView: matrix];
	[box setNextResponder:self];
	[matrix setNextResponder:self];
	
	lastItem = 0;
	lastrow = 0;
	target = nil;
	attachedMenu = nil;

	// process the menu string
	[self processMenuString: string];
	lastSelection=lastItem-1;

	return self;
}


int getNextMenuItem(char** string, char* buffer)
// returns 1 if has a submenu in it, 0 if its just an entry, -1 if error
// returns -2 if end of string no partial or whole items found
{
	int level=0, rval=0;
	char *p;
	
	p=buffer;
	//printf("string is (%s)\n", *string);
	while(isspace(**string)) (*string)++;
	//printf("after white strip, string is (%s)\n", *string);
	if(**string =='\0') return -2; 
	// look for the opening delimiter OPEND
	if(**string!=OPEND) return -1;
	// opening brace found, find matching
	//printf("doing the brace count thing\n");
	*p++ = **string; (*string)++; // add the brace to the buffer
	level=1;
	do {
		//printf("finding opening braces\n");	
		while(**string==OPEND) { level++; *p++ = **string; (*string)++; rval=1; }
		//printf("finding closing braces\n");	
		while(**string==CLOSED)	{ level--; *p++ = **string; (*string)++; }
		if(level<=0) break;
		//printf("finding regular text\n");	
		while(**string!=OPEND && **string!=CLOSED && **string!='\0') 
			{ *p++ = **string; (*string)++; }
		if(**string=='\0') break;
		//printf("level is %d, string after round is (%s)\n", level, *string);
	} while(level>0);
	if(level) return -1; // there must have been an error
	*p='\0';
	//printf("returning buffer as (%s), remainder (%s)\n", buffer, *string);
	return rval;
}


int extractMenuItemFromBrackets(char* string, char* buffer, SEL* sel, int *tag)
// assumes that string is like [ menuitem [ ...]]
// returns 1 if found string, but no closing brace
// 0 if closing brace found
// 2 if opening brace found ie submenu items
// -1 if error
{
	char *p=buffer,*ch, message[1024];
	int rval=0;
	
	tag=0;
	*sel=0;
#ifdef DEBUGIT	
	printf("extracting, string is (%s), buffer (%s)\n", string,buffer);
#endif	
	while(isspace(*string)) (string)++; // advance past initial whitespace
	if(*string!=OPEND) {
		strcpy(buffer,"No Open Brace");
		return -1;
	}
	string++; // advance past initial brace
	while(isspace(*string)) (string)++; // strip leading white on menu item
	// make sure we get multi-word menu items
	//printf("string is now (%s)\n", string);
	do {
		while(*string!=OPEND && *string!=CLOSED && *string!='\0') 
			*p++ = *string++;
		if(*string==OPEND) { *p=' '; rval=2; break; }
		if(*string==CLOSED) { *p=' '; rval=0; break; }
		if(*string=='\0') { *p=' '; rval=1; break; }
	} while(1);
	
	// remove trailing whitespace
	while(isspace(*p)) p--;
	p++;
	*p='\0';
	
	// now search for the selector and the tag
	p=buffer;
	while(*p!=SEPARATOR && *p!='\0') p++;
	if(*p=='\0') return rval;
	*p='\0'; // set the end of the title
	p++; // advance past the delimiter
	ch=message;
	while(*p!=SEPARATOR && *p!='\0') *ch++=*p++;
	if(*p=='\0') { *sel = sel_getUid(message); return rval; }
	*ch='\0';
	p++;
#ifdef DEBUGIT	
	printf("message would be (%s)\n", message);
#endif	
	*sel = sel_getUid(message);
	message[0]='\0';
	ch=message;
	while(*p!='\0' && !isspace(*p)) *ch++=*p++;
	*ch='\0';
	tag = atoi(message);
	
#ifdef DEBUGIT	
	printf("return from extraction should be (%s), extracted (%s)\n", string, buffer);
#endif	
	return rval;
}


int extractSubmenuFromBrackets(char* string, char* buffer)
// returns 0 if submenu found
// returns -1 if any type of error
// assumes that string is like [ menuItem [ [submenuItem1] [..2] ]]]
// will return just a plain list of items [submenuItem1] [..2] [..]
{
	char *p=buffer;

#ifdef DEBUGIT
	printf("extractsSubmenuFromBrackets(): string is (%s)\n", string);
#endif	
	// strip leading white
	while(isspace(*string)) string++;
	if(*string!=OPEND) {
		strcpy(buffer," [submenu.] [found.]   [in extracting] [brace] [No open] [Error!]");
		return -1;
	}
	// advance past open brace
	string++;
	// advance past possible multi-word menu item
	do {
		while(*string!=OPEND && *string!=CLOSED && *string!='\0') string++;
		if(*string=='\0') {
			strcpy(buffer,"[Error] [in extracting] [submenu.] [Looking] [for any] [delimiter]");
			return -1;
		}
		if(*string==CLOSED) {
			strcpy(buffer,"[Error] [in extracting.] [Submenu] [not found.]");
			return -1;
		}
		if(*string==OPEND) break;
	} while(1);

	// if we broke out here the cursor is on the open bracket, chop it
	string++;
	
	// copy the submenu
#ifdef DEBUGIT	
	printf("copying submenu\n");
#endif	
	while(*string!='\0') *p++ = *string++;

	// backup looking for a closing brace NEEDS MORE ERROR CHECKING HERE!
	while(*p!=CLOSED) p--;
	p--;
	while(*p!=CLOSED) p--;
	*p='\0'; // zap out last closing brace
	return 0;
}









- veryFirstSelection:(int)sel
{
	lastSelection=sel;
	return self;
}



- processMenuString:(char*)string
{
	char buffer[1024], title[1024];
	id cell, newMenu;
	SEL sel;
	int tag;
	
#ifdef DEBUGIT	
	printf("string is (%s)\n", string);
#endif	
	//REMOVE_LEADING_WHITE(&string);
	while(isspace(*string)) string++;
	if(*string!='[' || *string=='\0') {
		// add one item that says ERROR!
#ifdef DEBUGIT		
		printf("leading brace not found\n");
#endif		
		cell = [[matrix insertRowAt: 0] cellAt:0 :0];
		[cell setTitle: "ERROR 1st char"];
		return self;
	}
	do {
		switch(getNextMenuItem(&string,buffer)) {
			case -2:
#ifdef DEBUGIT
				printf("end of string\n");
#endif				
				goto end;
			case -1:
#ifdef DEBUGIT			
				printf("error occured");
#endif				
				goto end;
				break;
			case 0:
#ifdef DEBUGIT			
				printf("menu item found (%s)\n", buffer);
#endif				
				[matrix insertRowAt:lastItem];
				cell = [matrix cellAt:lastItem :0];
				extractMenuItemFromBrackets(buffer, title, &sel, &tag);
#ifdef DEBUGIT				
				printf("title of menu item shall be (%s)\n", title);
#endif				
				[cell setTitle:title];
				[cell setTag:tag];
#ifdef DEBUGIT				
				printf("action number is (%u)\n",sel);
#endif				
				[cell setAction:sel];
				[cell setEnabled: YES];
				[cell setHighlightsBy: NX_CHANGEGRAY];
				lastItem++;
				break;
			case 1:
#ifdef DEBUGIT			
				printf("menu item with submenu found (%s)\n", buffer);
#endif
				[matrix insertRowAt:lastItem];
				cell = [matrix cellAt:lastItem :0];
				extractMenuItemFromBrackets(buffer, title,&sel,&tag);
#ifdef DEBUGIT				
				printf("title of menu item shall be (%s)\n", title);
#endif				
				[cell setTitle:title];
				[cell setIcon: "NXmenuArrow"];
				lastItem++;
				// create a submenu
				extractSubmenuFromBrackets(buffer, title);
#ifdef DEBUGIT				
				printf("submenu extracted is (%s)\n", title);
#endif				
				newMenu=[[PopUpMenu alloc] initWithMenuString:title];
				[newMenu setSupermenu:self];
				[cell setTarget:newMenu];
				break;
		}
	} while(1);


end:
	return self;
}

- setSupermenu: menu
{
	supermenu = menu;
	return self;
}

- supermenu
{
	return supermenu;
}

- matrix
{ return matrix; }

- init
{
	char *menuitems="[Please use] [PopUpMenu ] [initWithMenuString:]";
	[self initWithMenuString: menuitems];
	return self;
}

- getLocation:(NXPoint *)theLocation forSubmenu:aSubmenu
{
	NXRect rect;

#ifdef DEBUGIT
	printf("finding attachedMenu location using lastrow (%d)\n", lastrow);
#endif
	[matrix getCellFrame:&rect at:lastrow :0];
	[matrix convertRect:&rect toView:nil];
	theLocation->x = NX_X(&frame) + NX_WIDTH(&frame) - 1.0;
	theLocation->y = NX_Y(&frame) + NX_Y(&rect) + NX_HEIGHT(&rect)/2.0 ;
	return self;
}


- shouldNotLoop
{
	shouldLoop=NO;
	return self;
}

// the following is not an inherited method, it slightly different then Cell's method
- (BOOL)trackMouse:(NXPoint*)viewPoint ofView: aView
{
	id cell;
	NXRect rect;
	NXPoint intPoint;
	int row, col;
	BOOL rval=NO;

	// convert screen point to this menu's windows coords
	intPoint = *viewPoint;
	[[aView window] convertBaseToScreen:&intPoint];
	[matrix getFrame:&rect];
	[self convertBaseToScreen:&rect.origin];
	intPoint.x -= NX_X(&frame);
	intPoint.y -= NX_Y(&frame);
	
	if(intPoint.x>0 && intPoint.y>0  && intPoint.x<NX_WIDTH(&frame)
		&& intPoint.y<NX_HEIGHT(&frame)) {
				if([matrix getRow: &row andCol: &col forPoint: &intPoint]!=nil) {
					// if lastrow==row, then just return since we are
					// already highlighted and we don't want to do it 
					// twice, UNLESS this cell has a menu and it's not
					// popped up yet
					cell = [matrix cellAt:row :0];
					if(lastrow==row && attachedMenu==nil && [cell icon]!=NULL) {
						attachedMenu = [cell target];
						[attachedMenu setTarget:target];
						[attachedMenu popUp:aView];
					}
					if(lastrow==row && ![cell isHighlighted]) {
						[matrix lockFocus];
						[matrix highlightCellAt:row :0 lit:YES];
						[matrix unlockFocus];
					}
					if(lastrow==row) return YES; // don't do unneeded highlighting

					[matrix lockFocus];
					[matrix highlightCellAt:lastrow :0 lit: NO];
					[matrix highlightCellAt:row :0 lit:YES];
					[matrix unlockFocus];
					
					[matrix selectCellAt:row :0];					
					lastrow = row;

					// new selections mean that any old attached menus should be popdowned
					if(attachedMenu!=nil) [attachedMenu popDown:self];
					if([cell icon]!=NULL) { // if there is a submenu for this item, pop it up
						attachedMenu = [cell target];
						[attachedMenu setTarget:target];
						[attachedMenu popUp:aView];
					} else attachedMenu=nil;
				}
				rval=YES;
	} else {
		// feed the events to any the attached menus to see if the viewPoint belongs to it
		if(attachedMenu!=nil) {
			if([attachedMenu trackMouse:viewPoint ofView:aView]) {
				// make sure we are highlighted, ie mouse came back into an attached menu
				[matrix lockFocus];
				[matrix highlightCellAt:lastrow :0 lit:YES];
				[matrix unlockFocus];
				rval=YES;
			}
			else {
				//[attachedMenu popDown:self];
				//attachedMenu=nil;
				[matrix lockFocus];
				[matrix highlightCellAt:lastrow :0 lit:NO];
				[matrix unlockFocus];
				rval=NO;
			}
		} else {
			[matrix lockFocus];
			[matrix highlightCellAt:lastrow :0 lit:NO];
			[matrix unlockFocus];
			rval=NO;
		}
	}
	return rval;
}


- popDown:sender
{
	// popdown attached menus also
	if(attachedMenu!=nil) [attachedMenu popDown: sender];
	attachedMenu=nil;
	// unlight selection
	[matrix lockFocus];
	[matrix highlightCellAt:lastrow :0 lit:NO];		
	[matrix unlockFocus];
	// order out self
	[self orderOut:sender];		
	return self;
}




- (BOOL)rightMouseUp:(NXPoint*)viewPoint ofView:aView
// returns yes if a selection was made
{
	id cell;
	NXRect rect;
	int row, col;
	NXPoint intPoint;
	BOOL rval=NO;

	row = lastSelection;

#ifdef DEBUGIT
	printf("rightMouseUp\n");
#endif	
	// give any attached menus a change to respond
	if(attachedMenu!=nil) {
#ifdef DEBUGIT	
		printf("giving attached menu a chance to respond\n");
#endif		
		if([attachedMenu rightMouseUp:viewPoint ofView:aView]) {
#ifdef DEBUGIT		
			printf("saving row information\n");
#endif			
			lastSelection = row = lastrow; /*[matrix selectedRow];*/
			rval = YES;
		}
	} else {
		// convert screen point to this menu's windows coords
		intPoint = *viewPoint;
		[[aView window] convertBaseToScreen:&intPoint];
		[matrix getFrame:&rect];
		[self convertBaseToScreen:&rect.origin];
		intPoint.x -= NX_X(&rect);
		intPoint.y -= NX_Y(&rect);
	
		if(intPoint.x>0 && intPoint.y>0  && intPoint.x<NX_WIDTH(&rect)
			&& intPoint.y<NX_HEIGHT(&rect)) {
					if([matrix getRow:&row andCol:&col forPoint:&intPoint]!=nil) {
						lastSelection = row;
						cell = [matrix cellAt:lastSelection :0];	
#ifdef DEBUGIT						
						printf("action number to call for title (%s) is (%u)\n",
								[cell title], [cell action]);			
#endif								
						if(target!=nil && [cell action]!=0) {
							// send the message to the target
							if([target respondsTo:[cell action]])
								[target perform:[cell action] with:cell];	
						}
					}
					rval = YES;
		}
	}

	// unlight cell so it pops up again looking okay
	[matrix lockFocus];
	[matrix highlightCellAt:row :0 lit:NO];		
	[matrix unlockFocus];
		
	return rval;
}


- popUp:aView 
{
    NXEvent       *nextEvent;
	NXRect			rect;
	NXPoint aPoint;
	BOOL highlightInitialCell=YES;


	if(!inited) {
		// resize the window to just hold the menu items
		[matrix sizeToFit];
		[boxView sizeToFit];
		[[self contentView] getBounds: &rect];
		[self sizeWindow: rect.size.width :rect.size.height];
		inited = YES;
	}

	[matrix getCellFrame:&rect at:lastSelection :0];
	[matrix convertRect:&rect toView:nil];

	if(supermenu!=nil) {
		// we must be a submenu, send message to supermenu to get popup location
		[supermenu getLocation:&aPoint forSubmenu:self];
		// offset self menu to account for mouse popping up in middle of a line
		// for a submenu.  We don't adjust x location because we want it to
		// pop up besides the supermenu's menu
		aPoint.y -= (NX_Y(&rect)+NX_HEIGHT(&rect)/2.0);
		highlightInitialCell=NO; // a submenu popup means mouse in supermenu window
		lastrow=-1;
	} else {
		// we are the main menu
		[[aView window] getMouseLocation:&aPoint];
		[[aView window] convertBaseToScreen:&aPoint];
		aPoint.x -= NX_WIDTH(&frame)/2.0;
		aPoint.y -= (NX_Y(&rect)+NX_HEIGHT(&rect)/2.0);
		highlightInitialCell=YES;
	}	
	
	// move window
#ifdef DEBUGIT	
	printf("last selection was %d\n", lastSelection);
#endif
	[self moveTo:aPoint.x :aPoint.y];

	[self display];
	[self orderFront:self];

	if(highlightInitialCell) {
		[matrix lockFocus];
		[matrix highlightCellAt:lastSelection :0 lit:YES];
		[matrix unlockFocus];
	}
	
	
	shouldLoop=YES;
	if(supermenu==nil) { // only get events if your the top supermenu dude!
    do {
		// location point is in terms of the base window that we popped up on top of!
		nextEvent = [NXApp getNextEvent:(NX_RMOUSEUPMASK |NX_RMOUSEDRAGGEDMASK
								|NX_MOUSEDOWNMASK|NX_MOUSEDRAGGEDMASK
								|NX_MOUSEENTEREDMASK|NX_MOUSEEXITEDMASK)];
		aPoint = nextEvent->location;
		//[[aView window] convertBaseToScreen:&(nextEvent->location)];
		//printf("event x,y=(%.0f,%.0f)\n", nextEvent->location.x,nextEvent->location.y);
		
        switch (nextEvent->type) {
        	case NX_RMOUSEUP:
            	shouldLoop = NO;
				[self rightMouseUp:&aPoint ofView:aView];
				[self popDown:self];
            	break;
        	case NX_RMOUSEDRAGGED:
				[self trackMouse:&aPoint ofView: aView];
            	break;
        	default:	
				// don't let anyone else have these action oriented mouse events
            	break;
        }
    } while(shouldLoop);
	}
    return(self);
}




- target
{
	return target;
}

- setTarget: newTarget
{
	target = newTarget;
	return self;
}
@end
