// // NSMenuView.m // mySTEP // // Created by Dr. H. Nikolaus Schaller on Thu Mar 27 2003. // Copyright (c) 2003 DSITRI. All rights reserved. // ///// selecting a menu item should set setDefaultButtonCell: so that 'return' selects #import #import #import #import #import #import #import #import #import #import "NSAppKitPrivate.h" #define VERTICAL_PADDING 3.0 // padding at top/bottom of vertical menus #define HORIZONTAL_PADDING 3.0 // padding at left/right of menu items #define SHORT_CLICK_INTERVAL 0.4 // in seconds @implementation NSMenuView - (BOOL) _isResizingHorizontally; { return _isResizingHorizontally; } - (void) _setHorizontalResize:(BOOL) flag; { // if set resize horizontal width as needed - otherwise leave untouched #if 0 NSLog(@"setHorizontalResize:%d", flag); #endif if(_isResizingHorizontally == flag) return; // unchanged _isResizingHorizontally=flag; _needsSizing=YES; } - (BOOL) _isStatusBar; { return _isStatusBar; } - (void) _setStatusBar:(BOOL) flag; { if(_isStatusBar==flag) return; // unchanged _isStatusBar=flag; _needsSizing=YES; } - (BOOL) _isContextMenu; { return _isContextMenu; } - (void) _setContextMenu:(BOOL) flag; { _isContextMenu=flag; } // standard extensions + (float) menuBarHeight; { #if 1 return 26.0; #else // 240 -> 16 // 320 -> 16 // 480 -> 24 // 640 -> 32 // 768 -> 38 // 1024 -> 51 static int h=0; if(h == 0 && [[NSScreen screens] count] > 0) { h=[[[NSScreen screens] objectAtIndex:0] frame].size.height*0.05; // 5% of screen size if(h < 12) h=12; // for small screens if(h > 24) h=24; // for large screens } return (float) h; #endif } - (void) attachSubmenuForItemAtIndex:(int) index; { NSRect r; NSMenu *submenu=[[_menumenu itemAtIndex:index] submenu]; #if 0 NSLog(@"attachSubmenuForItemAtIndex %d", index); #endif [[submenu delegate] menuNeedsUpdate:submenu]; // allow to update entries before really opening if([submenu numberOfItems] < 1) return; // ignore empty submenus if([_attachedMenuView menu] == submenu) return; // already attached [self detachSubmenu]; // detach any other submenus before attaching a new submenu [submenu update]; // enable/disable menu items if(!_menuWindow) { // create a new submenu window _menuWindow=[[NSPanel alloc] initWithContentRect:NSMakeRect(0.0, 0.0, 50.0, 50.0) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES]; // will be released on close [_menuWindow setWorksWhenModal:YES]; [_menuWindow setLevel:NSSubmenuWindowLevel]; #if 1 NSLog(@"win=%@", _menuWindow); NSLog(@"index=%d rect=%@", index, NSStringFromRect([self rectOfItemAtIndex:index])); NSLog(@"converted rect=%@", NSStringFromRect([self convertRect:[self rectOfItemAtIndex:index] toView:nil])); NSLog(@"autodisplay=%d", [_menuWindow isAutodisplay]); #endif } #if 1 [_menuWindow setTitle:[submenu title]]; #endif _attachedMenuView=[[isa alloc] initWithFrame:[[_menuWindow contentView] frame]]; // make new NSMenuView of matching size [_menuWindow setContentView:_attachedMenuView]; // make content view #if 0 NSLog(@"attachedMenuView=%@", _attachedMenuView); #endif [_attachedMenuView setHorizontal:NO]; // make submenus always vertical #if 0 NSLog(@"was setHorizontal:NO"); #endif [_attachedMenuView setMenu:submenu]; // define to manage selected submenu r=[self convertRect:[self rectOfItemAtIndex:index] toView:nil]; r.origin=[_window convertBaseToScreen:r.origin]; // convert to screen coordinates #if 0 NSLog(@"screen rect=%@", NSStringFromRect(r)); #endif [_attachedMenuView setWindowFrameForAttachingToRect:r onScreen:[_window screen] preferredEdge:(_isHorizontal?NSMinYEdge:NSMaxXEdge) // default: below or to the right popUpSelectedItem:0]; // this should resize the submenu window and show the first item [_menuWindow orderFront:self]; // finally, make it visible #if 0 NSLog(@"attachSubmenu done"); #endif } - (NSMenu *) attachedMenu; { return [_attachedMenuView menu]; } - (NSMenuView *) attachedMenuView; { return _attachedMenuView; } - (void) detachSubmenu; { NSPanel *win; if(_attachedMenuView) { [self setHighlightedItemIndex:-1]; // remove any highlighting [[self attachedMenu] setSupermenu:nil]; // detach supermenu [_attachedMenuView detachSubmenu]; // recursively detach #if 0 NSLog(@"detachSubmenu %@", _attachedMenuView); #endif win=(NSPanel *) [_attachedMenuView window]; _attachedMenuView=nil; // no longer attached [win orderOut:nil]; // make invisible but keep cached } } - (NSFont *) font; { return _font?_font:(_isStatusBar?[NSFont menuFontOfSize:0.0]:[NSFont menuBarFontOfSize:0.0]); } - (int) highlightedItemIndex; { return _highlightedItemIndex; } - (float) horizontalEdgePadding; { return _horizontalEdgePadding; } - (float) imageAndTitleOffset; { if(_needsSizing) [self sizeToFit]; return _imageAndTitleOffset; } - (float) imageAndTitleWidth; { if(_needsSizing) [self sizeToFit]; return _imageAndTitleWidth; } - (int) indexOfItemAtPoint:(NSPoint) point { int i; int nc=[_cells count]; for(i=0; i 10) // keep last 10 items _rectOfCells=(NSRect *) objc_realloc(_rectOfCells, [_cells count]*sizeof(NSRect)); // adjust size _needsSizing=YES; // recalculate [self setNeedsDisplay:YES]; } - (float) keyEquivalentOffset; { if(_needsSizing) [self sizeToFit]; return _keyEquivalentOffset; } - (float) keyEquivalentWidth; { if(_needsSizing) [self sizeToFit]; return _keyEquivalentWidth; } - (NSPoint) locationForSubmenu:(NSMenu *) submenu; { NSRect p; int idx; #if 1 NSLog(@"locationForSubmenu"); NSLog(@"should not be used!"); #endif if(_needsSizing) [self sizeToFit]; idx=[_menumenu indexOfItemWithSubmenu:submenu]; // locate submenu if(idx < 0) return [self frame].origin; p=_rectOfCells[idx]; // location of referenced menu item if(_isHorizontal) { // below/above p.origin.y-=p.size.height; // below } else { // right/left p.origin.x+=p.size.width; // right of menu // should check if submenu itself fits there } return p.origin; } - (NSMenu *) menu; { return _menumenu; } - (NSMenuItemCell *) menuItemCellForItemAtIndex:(int) index; { return [_cells objectAtIndex:index]; } - (BOOL) needsSizing; { return _needsSizing; } - (void) performActionWithHighlighingForItemAtIndex:(int) index; { NSMenuItemCell *c=[self menuItemCellForItemAtIndex:index]; [self setHighlightedItemIndex:index]; [self display]; [c performClick:[c menuItem]]; // delay by running loop for a certain interval [self setHighlightedItemIndex:-1]; // remove any highlighting [self display]; } - (BOOL) performKeyEquivalent:(NSEvent*)event { // find a menu item that responds to this key event return [_menumenu performKeyEquivalent:event]; } - (NSRect) rectOfItemAtIndex:(int) index; { if(_needsSizing) [self sizeToFit]; NSCParameterAssert(index >= 0 && index < [_cells count]); return _rectOfCells[index]; } - (void) setFont:(NSFont *) f; { if(!f) f=[NSFont menuFontOfSize:0]; // susbtitute default if(_font == f) return; ASSIGN(_font, f); _needsSizing=YES; } - (void) setHighlightedItemIndex:(int) index; { NSMenuItemCell *c; #if 0 NSLog(@"setHighlightedItemIndex: %d", index); #endif if(_highlightedItemIndex == index) return; // no change if(_highlightedItemIndex >= 0) { // lowlight previous [[self menuItemCellForItemAtIndex:_highlightedItemIndex] setHighlighted:NO]; // remove highlighting [self setNeedsDisplayForItemAtIndex:_highlightedItemIndex]; } _highlightedItemIndex=index; if(_highlightedItemIndex >= 0) { NSMenuItem *i; c=[self menuItemCellForItemAtIndex:_highlightedItemIndex]; i=[c menuItem]; // FIXME: always enable items having a private view if([i isSeparatorItem] || ![i isEnabled]) { // don't highlight separators or disabled items #if 0 NSLog(@"item is separator or not enabled"); #endif _highlightedItemIndex=-1; return; } [c setHighlighted:YES]; // add new highlighting [self setNeedsDisplayForItemAtIndex:_highlightedItemIndex]; } } - (void) setHorizontal:(BOOL) flag; { int i; int nc; if(_isHorizontal==flag) return; // no change #if 0 NSLog(@"setHorizontal:%d", flag); #endif _isHorizontal=flag; nc=[_cells count]; for(i=0; i= [m numberOfItems]) [self setHighlightedItemIndex:-1]; // remove highlighting } for(i=[m numberOfItems], cnt=[_cells count]; i 50) NSLog(@"set large menu with %d entries", cnt); _needsSizing=YES; // even if we have no cells... [_menumenu update]; // auto-enable and resize if needed } } - (void) setMenuItemCell:(NSMenuItemCell *) cell forItemAtIndex:(int) index; { [_cells replaceObjectAtIndex:index withObject:cell]; [cell setMenuItem:[[_menumenu itemArray] objectAtIndex:index]]; // make a reference to menu item to be shown [cell setMenuView:self]; // make reference to myself [cell setFont:[self font]]; // set font [cell setHighlighted:(_highlightedItemIndex==index)]; // may be the currently highlighted element _needsSizing=YES; // we will need to recalc size - which will also redisplay the full menu } - (void) setNeedsDisplayForItemAtIndex:(int) index; { [[_cells objectAtIndex:index] setNeedsDisplay:YES]; // mark cell to redraw itself [self setNeedsDisplayInRect:[self rectOfItemAtIndex:index]]; // we need redrawing for this cell } - (void) setNeedsSizing:(BOOL) flag; { _needsSizing=flag; } - (void) setWindowFrameForAttachingToRect:(NSRect) ref onScreen:(NSScreen *) screen preferredEdge:(NSRectEdge) edge popUpSelectedItem:(int) index; { NSRect mf; // new menu frame in content rect coordinates NSRect sf=[[_window screen] visibleFrame]; NSRect item=NSZeroRect; #if 1 NSLog(@"setWindowFrameForAttachingToRect:%@ screen:... edge:%d item:%d", NSStringFromRect(ref), edge, index); #endif if(_needsSizing) [self sizeToFit]; // this will initially resize the window and our frame/bounds to fit the full menu edge &= 3; mf.size=_frame.size; // copy content size if(index >= 0 && index < [_cells count]) item=[self rectOfItemAtIndex:index]; // get rect of item to show #if 1 NSLog(@"screen visble frame=%@", NSStringFromRect(sf)); NSLog(@"item rect=%@", NSStringFromRect(item)); #endif switch(edge) { // calculate preferred location case NSMinXEdge: // to the left mf.origin.x=ref.origin.x-mf.size.width; mf.origin.y=ref.origin.y+ref.size.height-mf.size.height+item.origin.y; // align top edge of selected item break; case NSMaxXEdge: // to the right mf.origin.x=ref.origin.x+ref.size.width; mf.origin.y=ref.origin.y+ref.size.height-mf.size.height+item.origin.y; // align top edge break; case NSMinYEdge: // below mf.origin.x=ref.origin.x-item.origin.x; mf.origin.y=ref.origin.y-mf.size.height; break; case NSMaxYEdge: // above mf.origin.x=ref.origin.x-item.origin.x; mf.origin.y=ref.origin.y+ref.size.height; break; } if(mf.origin.x < 0) // does not fit to the left - try to the right mf.origin.x=ref.origin.x+ref.size.width; if(mf.origin.x+mf.size.width > sf.size.width) // does (still) not fit to the right - try (again) to the left but align right border on horizontal menus mf.origin.x=ref.origin.x-mf.size.width+((edge==NSMinYEdge || edge==NSMaxYEdge)?ref.size.width:0.0); if((_needsScrolling=(mf.origin.x < 0))) { _neededSize=mf.size.width; mf.origin.x=0.0; // still no fit - needs horizontal scrolling mf.size.width=sf.size.width; // limit to screen } if(mf.origin.y < 0) // try above if it does not fit below mf.origin.y=ref.origin.y+((edge==NSMinYEdge || edge==NSMaxYEdge)?ref.size.height:0.0); if(mf.origin.y+mf.size.height > sf.size.height) // try below mf.origin.y=ref.origin.y-mf.size.height; if(mf.origin.y < 0) { _needsScrolling=YES; _neededSize=mf.size.height; mf.origin.y=0.0; // still no fit - needs vertical scrolling mf.size.height=sf.size.height; // limit to screen } #if 1 NSLog(@"set frame=%@", NSStringFromRect(mf)); #endif [_window setFrame:[_window frameRectForContentRect:mf] display:NO]; // this will also change our frame&bounds since we are the contentView! if(_needsScrolling && index >= 0) { // menu needs scrolling NSRect f=_frame; if(edge == NSMinYEdge) f.origin.y+=item.origin.y-VERTICAL_PADDING; // menu below if(edge == NSMinXEdge) f.origin.x+=item.origin.x-HORIZONTAL_PADDING; // menu left else ; // above or right [self setFrameOrigin:f.origin]; // move content up/down as needed } [self setNeedsDisplay:YES]; // needs display everything #if 1 NSLog(@"set frame done"); #endif } - (void) _calcMaxWidthOfCellComponents; { // get maximum width of all cell components unsigned int i; unsigned int nc; _imageAndTitleWidth=0.0; _keyEquivalentWidth=0.0; _stateImageWidth=0.0; for(i=0, nc=[_cells count]; i _stateImageWidth) _stateImageWidth=iw; // new maximum iw=[c imageWidth]; tw=[c titleWidth]; iatw=iw+tw; // image and title width if(iw > 0.0 && tw > 0.0) iatw+=_horizontalEdgePadding; // add padding if both elements are present if(iatw > _imageAndTitleWidth) _imageAndTitleWidth=iatw; // new maximum if((iw=[c keyEquivalentWidth]) > _keyEquivalentWidth) _keyEquivalentWidth=iw; // new maximum } } - (float) _calcHorizontalPositionOfCellComponents; { float x=_horizontalEdgePadding; // x: layout position - start with left-hand padding _stateImageOffset=x; // state image starts here if(_stateImageWidth > 0) x+=_stateImageWidth+_horizontalEdgePadding; // include space between state image/title _imageAndTitleOffset=x; // image&title starts here x+=_imageAndTitleWidth; // already includes padding between image and title if(_keyEquivalentWidth > 0) x+=_horizontalEdgePadding; // additional space between image/title and key equivalent _keyEquivalentOffset=x; // key equivalents start here x+=_keyEquivalentWidth; // total cell width ends here after keyEquivalent return x+_horizontalEdgePadding; // add right-hand padding } - (void) sizeToFit; { // calculate new size and positon of cells - handle horizontal vs. vertical - handle rightToLeft NSRect p; // cell position NSRect f; // frame size int i; int nc; if(!_needsSizing) return; #if 1 NSLog(@"sizeToFit %@", self); #endif if(!_window) { #if 1 NSLog(@" menu %@ sizeToFit has no window", [_menumenu title]); #endif return; // no reference frame (yet) } _needsSizing=NO; // will have been done when calling other methods (avoid endless recursion) f=[_window frame]; // get enclosing window frame #if 0 NSLog(@"window: %@", window); NSLog(@"frame before: %@", NSStringFromRect(f)); #endif nc=[_cells count]; if(nc > 50) NSLog(@"sizing large menu with %d entries", nc); if(_isHorizontal) { // horizontal menu _imageAndTitleWidth=0.0; // we don't know for a horizontal menu _keyEquivalentWidth=0.0; _stateImageWidth=0.0; _keyEquivalentOffset=_imageAndTitleOffset=_stateImageOffset=_horizontalEdgePadding; p.origin=NSMakePoint(_horizontalEdgePadding, 0.0); // initial position (top left) for(i=0; i 50) NSLog(@"drawing large menu with %d entries", nc); if(_needsSizing) NSLog(@"NSMenuView drawRect: please call sizeToFit explicitly before calling display"); // rect is most probably inaccurate #if 1 NSLog(@"NSMenuView - %@ (nc=%d) drawRect:%@", [[_menumenu itemAtIndex:0] title], nc, NSStringFromRect(rect)); #endif //// FIXME: the following code deletes all menu items in the drawing rectangle which may be the union of 2 non-adjacent cells! //// so this greys out the cells in between unless we redraw them all... [[NSColor windowBackgroundColor] set]; // draw white/light grey lines NSRectFill(rect); // draw background #if 0 // draw box around menu for testing { // draw box [[NSColor brownColor] set]; // shouldn't this be frame/bounds clipped by rect??? // everything else could generate artefacts NSFrameRect(rect); } #endif #if 0 NSLog(@"background filled"); #endif for(i=0; i 0.0) { // up arrow [path moveToPoint:NSMakePoint(NSMidX(bounds), NSMinY(bounds))]; [path lineToPoint:NSMakePoint(NSMidX(bounds)-ARROW, NSMinY(bounds)+ARROW)]; [path lineToPoint:NSMakePoint(NSMidX(bounds)+ARROW, NSMinY(bounds)+ARROW)]; [path closePath]; [path fill]; } if(NSMinY(bounds) < _neededSize-NSHeight(bounds)) { // down arrow [path moveToPoint:NSMakePoint(NSMidX(bounds), NSMaxY(bounds))]; [path lineToPoint:NSMakePoint(NSMidX(bounds)-ARROW, NSMaxY(bounds)-ARROW)]; [path lineToPoint:NSMakePoint(NSMidX(bounds)+ARROW, NSMaxY(bounds)-ARROW)]; [path closePath]; [path fill]; } } else { if(NSMinX(bounds) > 0.0) { // left arrow [path moveToPoint:NSMakePoint(NSMinX(bounds), NSMidY(bounds))]; [path lineToPoint:NSMakePoint(NSMinX(bounds)+ARROW, NSMidY(bounds)+ARROW)]; [path lineToPoint:NSMakePoint(NSMinX(bounds)+ARROW, NSMidY(bounds)-ARROW)]; [path closePath]; [path fill]; } if(NSMinX(bounds) < _neededSize-NSWidth(bounds)) { // right arrow [path moveToPoint:NSMakePoint(NSMaxX(bounds), NSMidY(bounds))]; [path lineToPoint:NSMakePoint(NSMaxX(bounds)-ARROW, NSMidY(bounds)+ARROW)]; [path lineToPoint:NSMakePoint(NSMaxX(bounds)-ARROW, NSMidY(bounds)-ARROW)]; [path closePath]; [path fill]; } } } } - (NSString *) description; { return [NSString stringWithFormat:@"%@ menu:%@ item[0]:%@ %@", NSStringFromClass([self class]), [_menumenu title], [_menumenu numberOfItems] > 0?[[_menumenu itemAtIndex:0] title]:@"?", _menumenu ]; } - (NSString *) _longDescription; { return [NSString stringWithFormat:@"%@ menu:%@ item[0]:%@ %@", [super description], [_menumenu title], [_menumenu numberOfItems] > 0?[[_menumenu itemAtIndex:0] title]:@"?", _menumenu ]; } - (BOOL) trackWithEvent:(NSEvent *) event; { NSPoint p; if(_attachedMenuView && [_attachedMenuView trackWithEvent:event]) return YES; // yes, it has been successfully handled by the submenu(s) p=[self convertPoint:[_window mouseLocationOutsideOfEventStream] fromView:nil]; // get coordinates relative to our window (we might have a different one as the event!) if([event type] == NSPeriodic && _needsScrolling) { NSRect rect=[self bounds]; BOOL change=YES; #define SCROLLAREA 30.0 #define SCROLLSTEP (16.0+VERTICAL_PADDING) // should be typical item height #if 0 NSLog(@"autoscroll menu"); NSLog(@"p: %@", NSStringFromPoint(p)); NSLog(@"nededSize: %f", _neededSize); #endif // FIXME: scrolling a Popup Menu should resize the whole window and there might be just one arrow! if(_isHorizontal) { if(p.x < 0.0) ; // in parent menu else if(p.x <= NSMinX(rect) + SCROLLAREA) // in top arrow area rect.origin.x=MAX(0.0, NSMinX(rect)-5*SCROLLSTEP); else if(p.x <= NSMinY(rect) + 3*SCROLLAREA) // in top arrow area rect.origin.x=MAX(0.0, NSMinX(rect)-SCROLLSTEP); else if(p.x > NSMaxY(rect)) ; // below menu else if(p.x >= NSMaxY(rect) - SCROLLAREA) // in bottom arrow area rect.origin.x=MIN(_neededSize-NSWidth(rect), NSMinX(rect)+5*SCROLLSTEP); else if(p.x >= NSMaxY(rect) - 3*SCROLLAREA) // in bottom arrow area rect.origin.x=MIN(_neededSize-NSWidth(rect), NSMinX(rect)+SCROLLSTEP); else change=NO; } else { // we are flipped... if(p.y < 0.0) ; // in parent menu else if(p.y <= NSMinY(rect) + SCROLLAREA) // in top arrow area rect.origin.y=MAX(0.0, NSMinY(rect)-5*SCROLLSTEP); else if(p.y <= NSMinY(rect) + 3*SCROLLAREA) // in top arrow area rect.origin.y=MAX(0.0, NSMinY(rect)-SCROLLSTEP); else if(p.y > NSMaxY(rect)) ; // below menu else if(p.y >= NSMaxY(rect) - SCROLLAREA) // in bottom arrow area rect.origin.y=MIN(_neededSize-NSHeight(rect), NSMinY(rect)+5*SCROLLSTEP); else if(p.y >= NSMaxY(rect) - 3*SCROLLAREA) // in bottom arrow area rect.origin.y=MIN(_neededSize-NSHeight(rect), NSMinY(rect)+SCROLLSTEP); else change=NO; } if(change) { #if 0 NSLog(@"new bounds: %@", NSStringFromRect(rect)); #endif [self setBoundsOrigin:rect.origin]; // scroll [self setNeedsDisplay:YES]; p=[self convertPoint:[_window mouseLocationOutsideOfEventStream] fromView:nil]; // get coordinates relative to our window (we might have a different one as the event!) } } if(NSMouseInRect(p, _bounds, [self isFlipped])) { // highlight (new) cell int item=[self indexOfItemAtPoint:p]; // get selected item #if 0 NSLog(@"item=%d", item); #endif if(item != _highlightedItemIndex) { // has changed [self setHighlightedItemIndex:item]; // highlight new item (which will initiate redisplay) if(item >= 0 && [[_menumenu itemAtIndex:item] hasSubmenu]) [self attachSubmenuForItemAtIndex:item]; // and open submenu if available else [self detachSubmenu]; // detach any open submenu hierarchy } return YES; } if(!_attachedMenuView) [self setHighlightedItemIndex:-1]; // unhighligt item if we leave the menu return NO; } - (void) mouseDown:(NSEvent *) theEvent; { // is not an NSControl so we must track ourselves NSTimeInterval menuOpenTimestamp=[theEvent timestamp]; NSMenuView *mv; int idx; BOOL stayOpen=NO; #if 1 NSLog(@"mouseDown:%@", theEvent); #endif [NSApp preventWindowOrdering]; [self update]; // update/enable menu(s) [NSEvent startPeriodicEventsAfterDelay:0.3 withPeriod:0.05]; while(YES) { // loop until mouse goes up NSEventType type=[theEvent type]; // FIXME: we may not even have to check this. If we are deactivated, we should simply hide the menu windows like any other panel if(![NSApp isActive]) // was deactivated (FIXME: do we ever see this as an event???) { // detach all open submenu items [self detachSubmenu]; break; } if(type == NSLeftMouseDown) { if(![self trackWithEvent:theEvent] && stayOpen) { // user clicked outside after a mouse up in this loop [NSApp postEvent:theEvent atStart:YES]; // re-queue break; // clicked outside of menu } } else if(type == NSLeftMouseUp) { if([theEvent timestamp]-menuOpenTimestamp > 0.5) break; // wasn't hold down long enough stayOpen=YES; } else if(type == NSMouseMoved || type == NSLeftMouseDragged || type == NSPeriodic) [self trackWithEvent:theEvent]; theEvent = [NSApp nextEventMatchingMask:GSTrackingLoopMask | NSPeriodicMask untilDate:[NSDate distantFuture] // get next event inMode:NSEventTrackingRunLoopMode dequeue:YES]; } [NSEvent stopPeriodicEvents]; // was generating scroll events mv=self; while([mv attachedMenuView]) mv=[mv attachedMenuView]; // go down to lowest open submenu level idx=[mv highlightedItemIndex]; #if 1 NSLog(@"item selected %d", idx); #endif [self setHighlightedItemIndex:-1]; // unhighligt my item [self detachSubmenu]; // detach all open submenu items if(idx >= 0) [[mv menu] performActionForItemAtIndex:idx]; // finally perform action - processes responder chain } // - (void) mouseDragged:(NSEvent *) theEvent; { return; } // - (void) mouseUp:(NSEvent *) theEvent; { return; } @end @implementation NSMenu (NSPopupContextMenu) + (void) popUpContextMenu:(NSMenu *) menu withEvent:(NSEvent *) event forView:(NSView *) view withFont:(NSFont *) font; { static NSPanel *win; static NSMenuView *menuView; NSRect r; int item; // item to pop up when scrolling #if 1 NSLog(@"popUpContextMenu %08x", menu); NSLog(@"popUpContextMenu %@", [menu title]); NSLog(@"popUpContextMenu event %@", event); NSLog(@"popUpContextMenu view %@", view); NSLog(@"popUpContextMenu font %@", font); #endif if(!menu || !event || !view) return; // [menu update]; // enable/disable menu items if(!win) { win=[[NSPanel alloc] initWithContentRect:NSMakeRect(49.0, 49.0, 49.0, 49.0) // some initial position styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES]; [win setWorksWhenModal:YES]; [win setLevel:NSSubmenuWindowLevel]; #if 0 [win setTitle:@"Context Menu"]; #endif menuView=[[[NSMenuView class] alloc] initWithFrame:[[win contentView] frame]]; // make new NSMenuView [menuView setFont:font]; // set default font [menuView setHorizontal:NO]; // make popup menu vertical [menuView _setContextMenu:YES]; // close on mouseUp [[win contentView] addSubview:menuView]; // add to view hiearachy } [menuView setMenu:menu]; // define to manage selected menu #if 0 NSLog(@"win=%@", win); NSLog(@"autodisplay=%d", [win isAutodisplay]); #endif r.origin=[[view window] convertBaseToScreen:[event locationInWindow]]; // to screen coordinates r.size=NSMakeSize(1.0, 1.0); #if 1 NSLog(@"menu to be attached to %@", NSStringFromRect(r)); #endif if([view respondsToSelector:@selector(selectedItem)]) item=[(NSPopUpButton *) view indexOfSelectedItem]; else item=0; // default if(item < 0 || item >= [menu numberOfItems]) item=0; [menuView setWindowFrameForAttachingToRect:r onScreen:[win screen] preferredEdge:NSMinYEdge // default: pull down popUpSelectedItem:item]; [win orderFront:self]; // make visible [menuView mouseDown:event]; // pass event down - runs a tracking loop [win orderOut:nil]; // and close after tracking ends } @end