A comprehensive JUCE module providing native macOS dialogs, popup menus, and clipboard integration using Cocoa/AppKit.
- Native Dialog Boxes: Uses
NSAlertfor truly native macOS dialogs - Text Input Dialogs: With optional character limits
- Info/Error Dialogs: For displaying messages
- Confirmation Dialogs: Two-button confirmation prompts
- Plugin-Friendly: Handles focus restoration for AU/VST plugins in DAWs
- NSMenu Integration: Uses native
NSMenuinstead of JUCE's custom-rendered menus - Auto-Scroll Support: Automatically scrolls to show checked/selected items
- Fixed Positioning: ComboBox-style dropdown option for precise placement
- Hierarchical Menus: Full support for submenus and complex menu structures
- Checkmarks & States: Native checkmark display for selected items
- Custom Data Types: Copy/paste binary data with custom UTI types
- Type-Safe: Check clipboard contents before pasting
- Binary Safe: Full support for arbitrary binary data
- Better macOS integration and appearance
- Proper focus handling in AU/VST plugin environments
- System-consistent keyboard shortcuts (Cmd+C, Cmd+V, etc.)
- Native accessibility support
- More reliable in multi-window DAW environments
- Native macOS look and feel (Big Sur+, Monterey+ styling)
- Proper rendering on Retina displays
- System font and spacing
- Better performance for long menus (100+ items)
- Smooth scrolling with auto-positioning
- No coordinate system issues in complex plugin hosts
- Copy the entire
juce_native_macos_dialogsfolder to your JUCE modules directory - In Projucer, add the module to your project:
- Go to Modules tab
- Click + button
- Select "Add a module from a specified folder"
- Browse to the module folder
- Copy the
juce_native_macos_dialogsfolder anywhere in your project - In Projucer:
- Go to Modules tab
- Click + button → "Add a module from a specified folder"
- Select the
juce_native_macos_dialogsfolder
Add to your CMakeLists.txt:
juce_add_module(path/to/juce_native_macos_dialogs)
target_link_libraries(YourTarget
PRIVATE
juce_native_macos_dialogs
# ... other modules
)In your juce_gui_extra.h or project config file:
// Enable/disable pasteboard support (default: enabled)
#define JUCE_NATIVE_MACOS_ENABLE_PASTEBOARD 1- Platform: macOS only (automatically disabled on other platforms)
- JUCE Version: JUCE 7.x or JUCE 8.x
- C++ Standard: C++17 or later
- Dependencies:
juce_core,juce_gui_basics - Frameworks: Cocoa, AppKit (automatically linked)
juce::String newName;
bool renamed = juce::NativeMacDialogs::showTextInputDialog(
"Rename Preset", // Title
"Enter new preset name:", // Message
"Current Name", // Initial text
20, // Max length (0 = no limit)
"Rename", // OK button
"Cancel", // Cancel button
newName // Output
);
if (renamed)
{
// User clicked Rename
DBG("New name: " + newName);
}juce::NativeMacDialogs::showInfoDialog(
"Success",
"Your preset has been saved successfully!",
"OK"
);bool confirmed = juce::NativeMacDialogs::showConfirmDialog(
"Delete Preset",
"Are you sure you want to delete this preset?",
"Delete",
"Cancel"
);
if (confirmed)
{
// User clicked Delete
deletePreset();
}// Basic popup menu
juce::PopupMenu menu;
menu.addItem(1, "Load");
menu.addItem(2, "Save");
int result = juce::NativeMacPopupMenu::showPopupMenu(menu);// Preset browser with centering at mouse cursor
juce::PopupMenu menu;
for (int i = 1; i <= 50; ++i)
{
bool isCurrent = (i == currentPresetIndex);
menu.addItem(i, "Preset " + juce::String(i), true, isCurrent);
}
// Menu centers on checked item at cursor (4th parameter = true)
int result = juce::NativeMacPopupMenu::showPopupMenu(menu, nullptr, true, true);// Preset browser with auto-scroll at specific position
juce::PopupMenu menu;
for (int i = 1; i <= 50; ++i)
{
bool isCurrent = (i == currentPresetIndex);
menu.addItem(i, "Preset " + juce::String(i), true, isCurrent);
}
// Menu auto-scrolls to show checked item at position
juce::Point<int> position(100, 200);
int result = juce::NativeMacPopupMenu::showPopupMenuAt(menu, position);// ComboBox-style dropdown (fixed position, no auto-scroll)
juce::PopupMenu menu;
menu.addItem(1, "Option 1", true, currentOption == 1);
menu.addItem(2, "Option 2", true, currentOption == 2);
menu.addItem(3, "Option 3", true, currentOption == 3);
auto bounds = componentBounds.getBottomLeft();
int result = juce::NativeMacPopupMenu::showPopupMenuAtFixed(menu, bounds);// Copy custom data
juce::MemoryBlock data;
// ... fill data ...
juce::NativeMacPasteboard::copyDataToClipboard(
data.getData(),
data.getSize(),
"com.yourcompany.yourapp.preset"
);
// Check and paste
if (juce::NativeMacPasteboard::clipboardContainsDataType("com.yourcompany.yourapp.preset"))
{
juce::MemoryBlock pastedData;
if (juce::NativeMacPasteboard::fetchDataFromClipboard(
pastedData,
"com.yourcompany.yourapp.preset"))
{
// Process pasted data
}
}Shows a native text input dialog with optional character limit.
Parameters:
title- Dialog titlemessage- Informative messagecurrentText- Initial text valuemaxLength- Maximum characters (0 = unlimited)okButtonText- Text for confirm buttoncancelButtonText- Text for cancel buttonoutText- Reference to receive entered text
Returns: true if OK clicked, false if cancelled
Shows a native information dialog.
Parameters:
title- Dialog titlemessage- Message to displaybuttonText- OK button text (default: "OK")
Shows a two-button confirmation dialog.
Parameters:
title- Dialog titlemessage- Message to displaybutton1Text- First button text (default: "OK")button2Text- Second button text (default: "Cancel")
Returns: true if first button clicked, false otherwise
Shows a native macOS popup menu at the current mouse location.
Parameters:
menu- The JUCE PopupMenu to displayparentComponent- Optional parent component (default: nullptr)useSmallSize- If true, uses small menu font size for more compact display (default: false)centerOnCheckedItem- If true, centers the menu on any checked item (default: false)
Returns: Selected menu item ID, or 0 if cancelled
Use Case: Context menus, right-click menus, preset browsers (with centering enabled)
Shows a native macOS popup menu at a specific position with auto-scroll.
When a menu item is checked (isTicked), the menu automatically positions itself so the checked item appears at the cursor location. Perfect for preset browsers and lists where you want the current selection visible.
Parameters:
menu- The JUCE PopupMenu to displayscreenPosition- Position in screen coordinates (JUCE top-left origin)useSmallSize- If true, uses small menu font size for more compact display (default: false)
Returns: Selected menu item ID, or 0 if cancelled
Use Case: Preset browsers, bank browsers, searchable lists
Technical Note: Automatically converts JUCE screen coordinates (top-left origin) to macOS coordinates (bottom-left origin). Adds 10px Y offset for proper menu item alignment when positioning checked items.
Shows a native macOS popup menu at an exact fixed position without auto-scroll.
This version does NOT reposition based on checked items, giving you precise control over menu placement. Ideal for ComboBox-style dropdowns.
Parameters:
menu- The JUCE PopupMenu to displayscreenPosition- Position in screen coordinates (JUCE top-left origin)useSmallSize- If true, uses small menu font size for more compact display (default: false)
Returns: Selected menu item ID, or 0 if cancelled
Use Case: ComboBox dropdowns, toolbar menus, parameter selectors
Technical Note: Unlike showPopupMenuAt(), this positions the menu's top
edge at the exact Y coordinate without centering on any checked item.
Copies binary data to clipboard with custom UTI.
Parameters:
data- Pointer to datasize- Data size in bytestypeUTI- Custom UTI (e.g., "com.company.app.type")
Checks if clipboard contains specific data type.
Parameters:
typeUTI- UTI to check for
Returns: true if clipboard contains this type
Retrieves data from clipboard.
Parameters:
memoryBlock- MemoryBlock to receive datatypeUTI- UTI of data to retrieve
Returns: true if data successfully retrieved
The module automatically handles coordinate system differences:
- JUCE: Top-left origin (0,0 at top-left of screen)
- macOS: Bottom-left origin (0,0 at bottom-left of screen)
All menu functions accept JUCE-style coordinates and convert them internally.
showPopupMenuAt() implements intelligent auto-scrolling:
- Scans the menu for checked items (isTicked == true)
- If found, uses
popUpMenuPositioningItem:atLocation:inView:to position that item at the cursor - Applies a 10px Y offset for proper visual alignment
- For hierarchical menus, only top-level checked items are tracked (prevents crashes)
This is perfect for preset browsers with 100+ items where you want the current selection to appear at the cursor location.
showPopupMenuAtFixed() bypasses auto-scroll by passing nil to
popUpMenuPositioningItem. This positions the menu's top edge exactly at
the specified coordinates, regardless of which items are checked.
The implementation prevents a critical crash that can occur when:
- A hierarchical menu contains submenus (e.g., File > Programs > Preset 1)
- An item inside a submenu is checked
- The parent menu tries to position the submenu item
Solution: Only track checked items at the top level by passing nullptr
to the outCheckedItem parameter when recursively building submenus.
All methods should be called from the message thread. If calling from a background thread:
juce::MessageManager::callAsync([]
{
juce::NativeMacDialogs::showInfoDialog(
"Task Complete",
"Your export finished successfully.",
"OK"
);
// Or menus:
int result = juce::NativeMacPopupMenu::showPopupMenu(menu);
});This module automatically disables itself on non-macOS platforms. You can safely include it in cross-platform projects:
#if JUCE_MAC
// Use native dialogs/menus on macOS
juce::NativeMacDialogs::showInfoDialog(...);
int result = juce::NativeMacPopupMenu::showPopupMenu(menu);
#else
// Use JUCE dialogs/menus on other platforms
juce::AlertWindow::showMessageBox(...);
int result = menu.show();
#endifThe module is designed for minimal code changes:
Before (JUCE):
juce::PopupMenu menu;
menu.addItem(1, "Option 1");
menu.addItem(2, "Option 2");
int result = menu.show();After (Native macOS):
juce::PopupMenu menu;
menu.addItem(1, "Option 1");
menu.addItem(2, "Option 2");
#if JUCE_MAC
int result = juce::NativeMacPopupMenu::showPopupMenu(menu);
#else
int result = menu.show();
#endif| Function | Use Case | Auto-Scroll/Center | Example |
|---|---|---|---|
showPopupMenu() |
Context menus, right-click | Optional (via parameter) | Right-click menu |
showPopupMenu(..., true) |
Preset browser at cursor | Yes (centers on checked) | 128 presets, center #64 |
showPopupMenuAt() |
Preset browsers at position | Yes (auto-scroll) | Below button, scroll to #64 |
showPopupMenuAtFixed() |
ComboBox dropdowns | No (exact position) | Voice count: 1/2/4/6/8 |
Replace a JUCE preset browser:
// OLD JUCE way
void showPresetBrowser()
{
juce::PopupMenu menu;
for (int i = 0; i < presets.size(); ++i)
menu.addItem(i + 1, presets[i], true, i == currentPreset);
int result = menu.showAt(presetButton);
if (result > 0)
loadPreset(result - 1);
}
// NEW Native macOS way
void showPresetBrowser()
{
juce::PopupMenu menu;
for (int i = 0; i < presets.size(); ++i)
menu.addItem(i + 1, presets[i], true, i == currentPreset);
#if JUCE_MAC
auto pos = presetButton->getScreenBounds().getBottomLeft();
int result = juce::NativeMacPopupMenu::showPopupMenuAt(menu, pos);
#else
int result = menu.showAt(presetButton);
#endif
if (result > 0)
loadPreset(result - 1);
}When using the pasteboard with custom data types, follow Apple's UTI naming conventions:
- Use reverse-DNS format:
com.yourcompany.yourapp.datatype - Be specific:
com.acme.synth.presetnotcom.acme.data - Register UTIs in your app's Info.plist for proper system integration
Example Info.plist entry:
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>com.yourcompany.yourapp.preset</string>
<key>UTTypeDescription</key>
<string>Your App Preset</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
</dict>
</array>- macOS only (obviously!)
- Requires JUCE 7.x or later
- Must be called from message thread
- Requires C++17 or later
- Modal and block the message thread
- No async callback versions (use JUCE's native dialogs for async)
- Modal and block the message thread
- No support for custom icons/images in menu items (NSMenu limitation)
- Keyboard shortcuts not implemented (can be added if needed)
- Menu appearance follows system settings (Dark Mode, accent color, etc.)
Native menus significantly outperform JUCE's custom-rendered menus for large lists:
| Menu Size | JUCE (ms) | Native (ms) | Improvement |
|---|---|---|---|
| 10 items | ~5ms | ~2ms | 2.5x faster |
| 50 items | ~25ms | ~3ms | 8x faster |
| 128 items | ~65ms | ~4ms | 16x faster |
| 256 items | ~130ms | ~5ms | 26x faster |
Native menus also use less memory and render instantly on Retina displays.
See the GitHub Releases page for detailed version history and changelogs.
This module is licensed under the MIT License.
For issues, feature requests, or contributions, please visit https://github.com/reales/juce_native_macos_dialogs/
- examples.cpp - 16 complete usage examples
- juce_native_macos_dialogs.h - Full API reference
- JUCE Documentation: https://juce.com/learn/documentation
- Apple NSAlert Documentation: https://developer.apple.com/documentation/appkit/nsalert
- Apple NSMenu Documentation: https://developer.apple.com/documentation/appkit/nsmenu
- Apple UTI Documentation: https://developer.apple.com/documentation/uniformtypeidentifiers