Add Custom Menu Component to Mac Main Menu

We have a Color Picker as custom component that we have added to a Juce Menu.
This works fine except for on OSX, on OSX we use the native menu and that does not support the customComponent’s that might have been set for the Menu Items.
The NSMenuItem that is used does allow for a subView, each NSMenuItem has a view property.
So i fooled around a bit by adding a NSButton to the item and that seems to work. But i haven’t managed yet to attach the Menu Items customComponent yet to the NSMenuItem.
Did anyone tried to do this before and has any suggestions? Any chance that JUCE could actually support the custom views?

What we want to accomplish is more or less what the Finder screenshot shows:

Assuming that a menu can contain an NSView then I guess this is totally possible, as you can use the NSViewComponent to convert any juce component into an NSView.

The NSViewComponent wraps a NSView in a Component, not the other way around. The NSMenuItem needs an NSView not a Component.

NSViewComponent
//==============================================================================
/**
A Mac-specific class that can create and embed an NSView inside itself.

To use it, create one of these, put it in place and make sure it's visible in a
window, then use setView() to assign an NSView to it. The view will then be
moved and resized to follow the movements of this component.

Of course, since the view is a native object, it'll obliterate any
juce components that may overlap this component, but that's life.

*/

I tried creating an NSView placeholder and calling addToDesktop on the custom item component with the NSView’s pointer as second argument but that didn’t work.

Sorry, yes, this is what I meant, not NSViewComponenent. It definitely does work, this is how things like plugins are implemented. Most likely you need to make sure the parent NSView is the right size, etc

Ok, i’ve fiddled around a bit and got it working, although it’s not done yet.
in juce_mac_MainMenu i’ve added this:

void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
const int topLevelMenuId, const int topLevelIndex)

		if (i.customComponent  != nullptr)
		{
        	int idealWidth = 80;
    		int idealHeight = 16;
			
			static std::vector< ReferenceCountedObjectPtr<PopupMenu::CustomComponent> >  customComponents;
			customComponents.push_back(i.customComponent);
			
			
			i.customComponent->getIdealSize (idealWidth, idealHeight);
			NSRect rect = NSMakeRect(0.0f, 0.0f, (float)idealWidth, (float)idealHeight);
			NSView* placeHolder = [[NSView alloc] initWithFrame:rect];
			i.customComponent->setVisible(true);
			i.customComponent->addToDesktop(0, placeHolder);
			i.customComponent->setBounds(0, 0, idealWidth, idealHeight);

			[item setView:placeHolder];
		}

This works but as you see we are leaking components here because i have a static vector of RefCounted components, without it of course no component would be visible. So the placeholder and the addToDesktop seem to work fine. Now we need a structure that will properly make sure to clean the custom components. Any chance you guys want to have a look at this?

Maybe we can add the i.customComponent to the MutableArray, but i’m not quiet sure how this should work.

Just a screenshot to show you what’s possible if the CustomCompontens work in the Mac menu

2 Likes

Ok , i finally fixed it by creating a NSView subclass called NSMenuItemView that holds a juce::ReferenceCountedObjectPtr to the customComponent it’s hosting.

@interface MenuItemView : NSView
{
	juce::ReferenceCountedObjectPtr<juce::PopupMenu::CustomComponent> customComponent;

}

- (id)initWithCustomComponent:( juce::ReferenceCountedObjectPtr<juce::PopupMenu::CustomComponent> ) customComponent_;
	

@end

@implementation MenuItemView
	
- (id)initWithCustomComponent:( juce::ReferenceCountedObjectPtr<juce::PopupMenu::CustomComponent> ) customComponent_
{
	
	int idealWidth = 80;
	int idealHeight = 16;
	customComponent_->getIdealSize (idealWidth, idealHeight);
	
	NSRect frame = NSMakeRect(0.0f, 0.0f, (CGFloat)idealWidth, (CGFloat)idealHeight);
	self = [super initWithFrame:frame];
	if (self)
	{
		customComponent = customComponent_;
		customComponent->setSize(idealWidth, idealHeight);
		customComponent->setVisible(true);
		customComponent->addToDesktop(0, self);
	}
	return self;
}

@end

When i.customComponent is not null i do this:

		if (i.customComponent != nullptr)
		{
			MenuItemView* view = [[MenuItemView alloc] initWithCustomComponent:i.customComponent];
			[item setView:view];
		}

This seems to work fine.

You should be a little wary of putting C++ objects inside an obj-C object - not sure whether it’s something that newer compilers got right, but in the past I’m pretty sure there could be problems with their constructors and destructors not being called correctly.

i could override the dealloc method and do the cleaning up myself. What else could you suggest?

Yeah, that’s typically what I do.

I’ve overridden the dealloc and added an autorelease when allocating my MenuItemView, now all get’s cleaned up properly. One last question though. When triggerMenuItem() is called i get a assertion because it’ searching in the parent-hierarchy for something that does not exist. Also the Mac Menu does not get dismissed.

void PopupMenu::CustomComponent::triggerMenuItem()
{
    if (HelperClasses::ItemComponent* const mic = findParentComponentOfClass<HelperClasses::ItemComponent>())
    {
        if (HelperClasses::MenuWindow* const pmw = mic->findParentComponentOfClass<HelperClasses::MenuWindow>())
        {
            pmw->dismissMenu (&mic->item);
        }
        else
        {
            // something must have gone wrong with the component hierarchy if this happens..
            jassertfalse;
        }
    }
    else
    {
        // why isn't this component inside a menu? Not much point triggering the item if
        // there's no menu.
		jassertfalse;
    }
}

What could i do here to make sure it dismisses the Mac Menu. Any thoughts, this seems to be the last step to get this working.