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.