This is PopUpMenu.m in view mode; [Download] [Up]
//#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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.