Reverse engineering an NSMenu for a Status Bar Item
Asked Answered
I

2

1

I'm want to create a menu for a status bar item like the one seen in Tapbot's PastebotSync application:

Does anyone have any ideas how to achieve the custom area at the top of the menu which is flush with the top?

I've tried/thought of a few potential ways of doing it:

  • Standard NSMenuItem with a view - isn't flush with the top of the menu
  • Some hack-ish code to place an NSWindow over the area at the top of the menu - not great as it doesn't fade out nicely with the menu when it closes
  • Abandoning an NSMenu entirely and using an NSView instead - haven't tried this yet but I don't really want to have to make some fake buttons or something that act as NSMenuItems

Anyone have any better ideas or suggestions?

Thanks!

Insulation answered 31/10, 2010 at 18:11 Comment(0)
P
4

In case anyone comes looking, I posted a solution to this at Gap above NSMenuItem custom view

Here's the code:

@interface FullMenuItemView : NSView
@end

@implementation FullMenuItemView
- (void) drawRect:(NSRect)dirtyRect
{
    NSRect fullBounds = [self bounds];
    fullBounds.size.height += 4;
    [[NSBezierPath bezierPathWithRect:fullBounds] setClip];

    // Then do your drawing, for example...
    [[NSColor blueColor] set];
    NSRectFill( fullBounds );
}
@end

Use it like this:

CGFloat menuItemHeight = 32;

NSRect viewRect = NSMakeRect(0, 0, /* width autoresizes */ 1, menuItemHeight);
NSView *menuItemView = [[[FullMenuItemView alloc] initWithFrame:viewRect] autorelease];
menuItemView.autoresizingMask = NSViewWidthSizable;

yourMenuItem.view = menuItemView;
Piker answered 7/3, 2011 at 2:44 Comment(0)
C
1

I had the same need in early versions of HoudahSpot 2. I did get it working with one limitation: my code leaves the menu with square corners at the bottom.

I have since abandonned this setup, as the BlitzSearch feature in HoudahSpot grew to need a complexer UI, I ran into other limitations with using NSViews in a NSMenu.

Anyway, here is the original code taking care of those extra 3 pixels:

- (void)awakeFromNib
{
 HIViewRef contentView;
 MenuRef menuRef = [statusMenu carbonMenuRef];
 HIMenuGetContentView (menuRef, kThemeMenuTypePullDown, &contentView);

 EventTypeSpec hsEventSpec[1] = {
  { kEventClassMenu, kEventMenuCreateFrameView }
 };

 HIViewInstallEventHandler(contentView,
          NewEventHandlerUPP((EventHandlerProcPtr)hsMenuCreationEventHandler),
          GetEventTypeCount(hsEventSpec),
          hsEventSpec,
          NULL,
          NULL);
}


#pragma mark -
#pragma mark Carbon handlers

static OSStatus hsMenuContentEventHandler( EventHandlerCallRef caller, EventRef event, void* refcon )
{
 OSStatus  err;

 check( GetEventClass( event ) == kEventClassControl );
 check( GetEventKind( event ) == kEventControlGetFrameMetrics );

 err = CallNextEventHandler( caller, event );
 if ( err == noErr )
 {
  HIViewFrameMetrics  metrics;

  verify_noerr( GetEventParameter( event, kEventParamControlFrameMetrics, typeControlFrameMetrics, NULL,
          sizeof( metrics ), NULL, &metrics ) );

  metrics.top = 0;

  verify_noerr( SetEventParameter( event, kEventParamControlFrameMetrics, typeControlFrameMetrics,
          sizeof( metrics ), &metrics ) );
 }

 return err;
}

static OSStatus hsMenuCreationEventHandler( EventHandlerCallRef caller, EventRef event, void* refcon )
{
 OSStatus  err = eventNotHandledErr;

 if ( GetEventKind( event ) == kEventMenuCreateFrameView)
 {
  err = CallNextEventHandler( caller, event );
  if ( err == noErr )
  {
   static const EventTypeSpec  kContentEvents[] =
   {
    { kEventClassControl, kEventControlGetFrameMetrics }
   };

   HIViewRef          frame;
   HIViewRef          content;

   verify_noerr( GetEventParameter( event, kEventParamMenuFrameView, typeControlRef, NULL,
           sizeof( frame ), NULL, &frame ) );
   verify_noerr( HIViewFindByID( frame, kHIViewWindowContentID, &content ) );
   HIViewInstallEventHandler( content, hsMenuContentEventHandler, GetEventTypeCount( kContentEvents ),
            kContentEvents, 0, NULL );
  }
 }

 return err;
}

Sorry, I forgot that bit:

- (MenuRef) carbonMenuRef
{
    MenuRef carbonMenuRef = NULL;

    if (carbonMenuRef == NULL) {
        extern MenuRef _NSGetCarbonMenu(NSMenu *);
        carbonMenuRef = _NSGetCarbonMenu(self);

        if (carbonMenuRef == NULL) {
            NSMenu *theMainMenu = [NSApp mainMenu];
            NSMenuItem *theDummyMenuItem = [theMainMenu addItemWithTitle: @"sub"  action: NULL keyEquivalent: @""];

            if (theDummyMenuItem != nil) {
                [theDummyMenuItem setSubmenu:self];
                [theDummyMenuItem setSubmenu:nil];
                [theMainMenu removeItem:theDummyMenuItem];

                carbonMenuRef = _NSGetCarbonMenu(self);
            }
        }
    }

    if (carbonMenuRef == NULL) {
        extern MenuRef _NSGetCarbonMenu2(NSMenu *);
        carbonMenuRef = _NSGetCarbonMenu2(self);
    }

    return carbonMenuRef;
}
Circumscribe answered 31/10, 2010 at 21:42 Comment(8)
The code looks promising but I haven't been able to get it working yet. It generates lots of errors and warnings when building in Xcode for me, perhaps all this is deprecated now?Insulation
I've actually got it building without errors after replacing [menu carbonMenuRef] with _NSGetCarbonMenu(menu) but it doesn't seem to affect on the menu.Insulation
The important line is "metrics.top = 0;"Circumscribe
I cannot get it to work either, it seems [menu carbonMenuRef] returns NULL.Inexplicable
Hi, i am looking for the same solution. I tried your code but it is giving errors. Please provide us sample project for the above code. I'll be grateful to you. ThanksWhidah
Hi, I have solved the EXC_BAD_ACCESS on the line InstallControlEventHandler, by replace "InstallControlEventHandler" to HIViewInstallEventHandler . I got that clue from apple developer samples "RecentItems". Hope this helpsWhidah
My current development SDK is 10.6Whidah
please check and comment on #6634343Whidah

© 2022 - 2024 — McMap. All rights reserved.