Native popup menu?

How can I get native popup menus in JUCE?

I noticed that Max/MSP (built with JUCE) uses native macOS popups.

thanks!

6 Likes

I’ve been looking around the forum, skimming through the docs and examples projects, and i still haven’t found the answer.

Have you figured it out?

Is this something vaguely planned or definitely unattainable?
Is anybody did it somewhere?

I did something like that for iOS popup menus : Displaying an UITableView in a JUCE iOS app? , it works pretty well as a drop-in replacement for juce menus (without fancy stuff). So I assume the same would be doable for native cocoa menus, although the api must be quite different…

2 Likes
#include <AppKit/NSMenu.h>

namespace juce {
  
  NSMenu* createNSMenu(const PopupMenu&, const String&, int, int, bool);
  
}


void NativePopupMenu::show(juce::PopupMenu& menu, juce::Component* associatedComponent)
{
  jassert(associatedComponent != nullptr);
  
  auto macOSMenu = juce::createNSMenu(menu, "", 0, 0, true);
  [macOSMenu popUpMenuPositioningItem:nil  atLocation:[NSEvent mouseLocation]  inView:nil];
  [macOSMenu release];
}

This can probably be further optimised and enhanced, but it is working for me so far. associatedComponent is not being used so far, but could be something for the future when the menu is not created directly at the mouse location etc.

EDIT: there was a release missing

4 Likes

could you give a working example in a plugin for dummy like me?

tried to put your code above in one of my module.mm. got an assertion because it’s not an app

// this can't be used in a plugin!
        jassert (JUCEApplicationBase::isStandaloneApp());

when i commented the assert, got 2nd assert here

NSMenu* createNSMenu (const PopupMenu& menu, const String& name, int topLevelMenuId, int topLevelIndex, bool addDelegate)
{
    initialiseMacMainMenu();

    if (auto* mm = JuceMainMenuHandler::instance)
        return mm->createMenu (menu, name, topLevelMenuId, topLevelIndex, addDelegate);

    jassertfalse; // calling this before making sure the OSX main menu stuff was initialised?
    return nil;
}

commenting on both assert, nothing happened when clicking button that assigned to the particular menu async.

and how do you get the callback?

this works

for anyone bump into this thread

// .h
#if JUCE_MAC
#include <Cocoa/Cocoa.h>
namespace menu
{
void showNative (void* nsViewHandle,
                 const std::map<int, juce::String>& map,
                 std::function<void (int)> onSelect);

void showNativeOptions (void* nsViewHandle,
                        const std::map<int, juce::String>& map,
                        int selectedKey,
                        std::function<void (int)> onSelect);

}// namespace menu
#endif// JUCE_MAC
// .mm
// your header includes if necessary
#if JUCE_MAC

// Objective‑C helper@interfaceclas
@interface MenuTarget : NSObject
{
    std::function<void(int)> callback;
}
+ (instancetype)sharedInstanceWithCallback:(std::function<void(int)>)cb;
- (void)menuItemSelected:(id)sender;
@end

@implementation MenuTarget

static MenuTarget* sharedInstance = nil;

+ (instancetype)sharedInstanceWithCallback:(std::function<void(int)>)cb
{
    if (sharedInstance == nil)
        sharedInstance = [[MenuTarget alloc] init];

    sharedInstance->callback = std::move(cb);
    return sharedInstance;
}

- (void)menuItemSelected:(id)sender
{
    if (callback)
        callback((int)[(NSMenuItem*)sender tag]);
}
@end

namespace menu
{

void showNative (void* nsViewHandle,
                 std::map<int, juce::String>& map,
                 std::function<void(int)> onSelect)
{
    // Cast the generic handle back to an
    NSView*NSView* parentView = (NSView*) nsViewHandle;

    NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Context"];

    for (const auto& [tag, value] : map.get())
    {
        auto rawItem = value.toRawUTF8();
        NSString* title = [NSString stringWithUTF8String:rawItem];
        NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:title
                                                      action:@selector(menuItemSelected:)
                                               keyEquivalent:@""];
        [item setTag:tag];
        [item setTarget:[MenuTarget sharedInstanceWithCallback:onSelect]];
        [menu addItem:item];
    }

    [NSMenu popUpContextMenu:menu
               withEvent:[NSApp currentEvent]
                 forView:parentView];

}

void showNativeOptions (void* nsViewHandle,std::map<int, juce::String>& map,int selectedKey,std::function<void(int)> onSelect){NSView* parentView = (NSView*) nsViewHandle;NSMenu* menu = [[NSMenu alloc] initWithTitle:@“Context”];

    for (const auto& [tag, value] : map.get())
    {
        auto rawItem = value.toRawUTF8();
        NSString* title = [NSString stringWithUTF8String:rawItem];
        NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:title
                                                      action:@selector(menuItemSelected:)
                                               keyEquivalent:@""];
        
        [item setTag:tag];
        [item setTarget:[MenuTarget sharedInstanceWithCallback:onSelect]];
        
        // Mark the selected item
        if (tag == selectedKey)
            [item setState:NSControlStateValueOn]; // shows a checkmark
        
        [menu addItem:item];
    }

    [NSMenu popUpContextMenu:menu
               withEvent:[NSApp currentEvent]
                 forView:parentView];

}

}// namespace menu
#endif // JUCE_MAC

This could be useful: GitHub - reales/juce_native_macos_dialogs: A JUCE module providing native macOS dialogs, popup menus, and clipboard integration using Cocoa/AppKit. Features NSAlert dialogs, NSMenu with auto-scroll support, and custom UTI clipboard operations. Drop-in replacement for JUCE components with better performance and native macOS appearance. Perfect for AU/VST plugins.

1 Like

In case any one else is wondering:

license:          GPL
1 Like

Nope, it’s MIT licensed.

1 Like

As of three hours ago. Until then it was GPL…

2 Likes

Posted a repo that actually may solve this, and the only question is about the license? Not whether it works, just the paperwork. Brilliant.

1 Like

Probably because GPL is NOT “just the paperwork”. :grinning_face_with_smiling_eyes:

2 Likes

It’s quite simple: with a license like GPL, commercial usage it out of the question, so that’s a roadblock. If I can’t use it, what’s the point of me looking and commenting? It might as well not exist.

I also think most people don’t have a use for this. It’s macOS only, so it doesn’t solve the same problem on Windows and Linux, so even for people who need this, it doesn’t solve their problem entirely.

This code is only of use if you create a macOS only app, need blocking, native looking dialogs, on top of a completely different than native looking UI.

From my point of view, there is simply not much interest in this from the larger JUCE developer community.

In my case i ended up by not use popup menus at all.

Thanks for sharing!

2 Likes

Right, let’s have a little chat about these points, shall we?

Though I should mention the author’s released this under MIT licence now anyway, so the entire GPL commercial usage debate is rather moot at this point. But just for the record: the assertion that GPL makes “commercial usage out of the question” is, how shall I put this… complete nonsense. You can absolutely flog GPL software commercially and you can use GPL code alongside proprietary bits through proper modularisation, process separation, or LGPL variants. It’s not quite the apocalyptic binary choice you’re painting it as.

Now, regarding this “macOS only, therefore pointless” gem: macOS represents roughly 60% of the music production software development market. Professional studios are overwhelmingly Mac-centric. For JUCE audio developers specifically, macOS is the primary platform, not some obscure hobby OS. Creating macOS-specific utilities isn’t ignoring everyone else… it’s addressing the majority platform. Not every solution needs to tick every box on day one to have value. Shocking, I know.

As for there being “not much interest from the larger JUCE developer community”, native-looking dialogs are a perennial complaint in JUCE development circles. The fact that you don’t need this particular solution doesn’t magically mean nobody else does. Just because something doesn’t solve your specific problem doesn’t render it useless to the rest of us. Novel concept, that.

2 Likes

This is awesome. Using it in all my Mac builds now. Makes the software feel more professional on Mac imo. Modified it a bit to be non-blocking and a few other improvements. On windows popups will just use my generic look and feel which is based off the Mac look anyways

The point is not so much the look but behavior.

Native menus behave a certain way in response to mouse movements that emulated menus do not (as far as I know). This is especially critical with nested submenus. Protecting muscle memory and habits is a convenience that users appreciate very much.