I’m using JUCE 5.4.7 and using Mac OS X Catalina with XCode 11.3.1. I’m building a sandboxed app and trying to get the “Open Recent” menu to work. I’m using this line of code when the menu is created:
juce::MenuBarModel::setMacMainMenu(mainMenuModel.get(), &macAppMenu, TRANS(“Open Recent”));
When the app runs and the menu is created, I get the application menu (the one next to the Apple icon) and the “File” menu (under which the “Open Recent” menu is supposed to appear as a submenu), but nothing else. (The app has “File”, “Edit”, “Insert”, “View”, “Media” and “Help” menus but only the “File” menu shows.) Neither of the two menus opens up when I click on them (they are both empty). The app doesn’t crash and otherwise runs fine, with the exception that its app menu isn’t working.
If I remove the third argument to setMacMainMenu() everything works fine and all of our menus and submenus appear (but if I select a file from our own “Recent Files” menu that we wrote ourselves, the OS won’t let the app open the file). I’m also using RecentlyOpenedFilesList::registerRecentFileNatively() to register files that the user opens. I see a “RecentFilesMenuTemplate.nib” in my project directory and a little debugging showed me that it’s being loaded (juce_Mac_MainMenu.mm line 363 returns an NSNib* object). There are a few forum topics from 2012 about this but I can’t find any answers there.
Does anyone have any idea what I could be doing wrong?
For anyone else trying to solve this issue: JUCE’s solution to the recent files menu seems to be a kludge that doesn’t work… I got this working perfectly using “bookmarks” and our own implementation of the recent files menu.
How are you able to retrieve the bookmark for a file on Mac, though?
Here’s the interface I came up with for managing them (hope it’s helpful–it’ll at least show you the objc calls to retrieve/activate them). The Apple docs were helpful–if I remember correctly you need to add an entitlement to your app for it to work… I store the bookmarks themselves as MemoryBlock objects in the app’s preferences file:
This is basically just a unique_ptr-type wrapper around Apple’s NSURL object (the url gets stored when the bookmark is “activated”):
class OSXBookmark {
void* url = nullptr;
public:
OSXBookmark() {}
~OSXBookmark();
bool activateBookmark(juce::MemoryBlock& bookmark);
void deactivateBookmark();
static juce::MemoryBlock getBookmarkForFile(const juce::String& filename);
OSXBookmark(const OSXBookmark&) = delete;
OSXBookmark(OSXBookmark&& other) : url(other.url) { other.url = nullptr; }
OSXBookmark& operator=(const OSXBookmark& other) = delete;
OSXBookmark& operator=(OSXBookmark&& other) {
url = other.url;
other.url = nullptr;
return *this;
}
};
Function to get the bookmark as a MemoryBlock:
juce::MemoryBlock OSXBookmark::getBookmarkForFile(const juce::String& filename) {
@autoreleasepool {
NSURL* u = [NSURL fileURLWithPath:[NSString stringWithUTF8String:filename.toRawUTF8()]];
NSData* d = [u bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:nil];
return d ? juce::MemoryBlock{[d bytes], [d length]} : juce::MemoryBlock{};
}
}
Function to activate the bookmark using the MemoryBlock data (memory block might be replaced with new bookmark data):
bool OSXBookmark::activateBookmark(juce::MemoryBlock& bookmark) {
@autoreleasepool {
bool r = false;
if (!url) {
NSData* d = [NSData dataWithBytes:bookmark.getData() length:bookmark.getSize()];
if (!d)
return false;
BOOL st = NO;
url = [[NSURL URLByResolvingBookmarkData:d options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&st error:nil] retain]; // done when app is relaunched
if (st) {
d = [(NSURL*)url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:nil];
if (d) {
bookmark.replaceWith([d bytes], [d length]);
r = true;
}
}
}
[(NSURL*)url startAccessingSecurityScopedResource];
return r;
}
}
void OSXBookmark::deactivateBookmark() {
@autoreleasepool {
[(NSURL*)url stopAccessingSecurityScopedResource];
}
}
Destructor:
OSXBookmark::~OSXBookmark() {
@autoreleasepool {
[(NSURL*)url release];
}
}
Thanks so much. I think this is just what I was looking for!