Generated reference › API — ICoreEssentials/UI/Backends/AppKit/MenuBar
kind: generated#api#icoreessentials-ui-backends-appkit-menubar

API — ICoreEssentials/UI/Backends/AppKit/MenuBar

The public contract of 1 header(s) under ICoreEssentials/UI/Backends/AppKit/MenuBar — 1 class/struct definition(s), 39 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.

HeaderDefinesDeclarationsBases
ICoreAppKitMenuBar.hICoreAppKitMenuBar39—

ICoreAppKitMenuBar.h#

ICoreEssentials/UI/Backends/AppKit/MenuBar/ICoreAppKitMenuBar.h

ICoreAppKitMenuBar#

ICoreAppKitMenuBar.h:29 · class · 39 declaration(s)

The AppKit backend's system menu bar: real NSMenu / NSMenuItem objects in the strip beside the Apple logo.

class ICoreAppKitMenuBar {
public:
    // ---- the chord split ---------------------------------------------------

    // Whether this chord may be handed to a menu item as its key equivalent.
    //
    // True for anything carrying a modifier, and for the function keys. FALSE
    // for a bare printable or editing key, and that exclusion is the point: the
    // system answers a key equivalent BEFORE the focused control ever sees the
    // key, so a bare Delete on a menu row would eat every deletion in every
    // text field in the application. Those chords go to the registry instead,
    // where the text-entry stand-down can still reach them.
    [[nodiscard]] static bool chordIsSafeAsKeyEquivalent(std::uint32_t chord);

    // Chord -> what NSMenuItem wants: the key-equivalent string (UTF-8) and the
    // modifier mask. False if the chord has no representable equivalent, in
    // which case neither output is touched.
    //
    // ⚠⚠ SHIFT DECIDES THE CASE OF THE STRING. AppKit matches the equivalent
    // against the event's -charactersIgnoringModifiers, which ignores every
    // modifier EXCEPT Shift, so ⇧⌘N must be spelled @"N" and ⌘S must be
    // spelled @"s". Setting the mask bit and leaving the string lowercase is
    // the spelling that looks right and never fires. Measured both ways round;
    // the table is in the .mm.
    //
    // ⚠ Shift + a printable NON-letter is REFUSED (false). ⇧1 is "!" on a US
    // board and something else on every other one, so no equivalent written
    // from the chord alone is right on more than one keyboard. Those go to the
    // shortcut registry, which compares modifier bits rather than glyphs.
    static bool chordToKeyEquivalent(std::uint32_t chord,
                                     std::string& outKeyEquivalent,
                                     unsigned long long& outModifierMask);

    // ---- where a row really belongs on this desktop ------------------------

    // Three rows do not live where an application declares them. macOS puts
    // About, Preferences and Quit in the APPLICATION menu, and a Mac user looks
    // for them there and nowhere else -- an app with "Exit" at the bottom of its
    // File menu reads as a port, because that is exactly what it is.
    //
    // ⚠ THE Qt BACKEND ALREADY DOES THIS, WHICH IS WHY NOT DOING IT WOULD BE A
    // REGRESSION RATHER THAN A MISSING FEATURE. QAction::menuRole defaults to
    // TextHeuristicRole, so Qt reads the row's TEXT and relocates it; this tree
    // never sets a role explicitly (searched: zero hits), so the heuristic is
    // what runs today on macOS. Reproducing it keeps the two backends showing
    // the same menus.
    enum Role : int { RoleNone = 0, RoleAbout = 1, RolePreferences = 2, RoleQuit = 3 };

    // The role of a row with this text, by the same reading Qt's heuristic
    // makes. `&` mnemonics are stripped first.
    //
    // ⚠ A DELIBERATE SUBSET, and the divergence is stated rather than hidden:
    // Qt's heuristic also recognises Cut/Copy/Paste/Select-All roles, which on
    // Cocoa only matter for a standard Edit menu Qt can synthesise. This
    // application builds its own Edit menu and those rows stay in it on BOTH
    // backends, so reproducing them here would move rows Qt leaves alone.
    [[nodiscard]] static int roleForItemText(const std::string& text);

    // ---- building the bar --------------------------------------------------

    // A fresh NSMenu to hang top-level menus from, ALREADY CARRYING THE
    // APPLICATION MENU (see the .mm: the first slot is not the app's to use).
    // Autoenabling is off on every menu this class makes.
    [[nodiscard]] static void* createMenuBar();

    // Whether this process has an application object at all. False in a
    // `--console` run that never brought one up.
    //
    // ⚠⚠ WITHOUT ONE, THIS CLASS FAILS SILENTLY IN TWO PLACES, AND BOTH LOOK
    // LIKE SUCCESS. Measured, no crash and no warning:
    //
    //   installAsMainMenu()      -[nil setMainMenu:] is a no-op, so the bar is
    //                            simply never installed and mainMenu() is null
    //   performKeyEquivalent()   returns TRUE for a matching row and runs
    //                            NOTHING -- the action is dispatched through
    //                            the application object, which is not there
    //
    // The second is the dangerous one: a caller using that BOOL to decide
    // whether to pass a key on would swallow every accelerator in the process.
    // Ask this before believing either result.
    [[nodiscard]] static bool hasApplicationObject();

    // [NSApp setMainMenu:]. The bar becomes the one on screen. Returns false if
    // there is no application object to install it into, rather than reporting
    // a success that did not happen.
    static bool installAsMainMenu(void* menuBar);
    [[nodiscard]] static void* mainMenu();

    // Ties `menuBar` to the window holding `hostView` (an NSView*): it becomes
    // the main menu whenever that window becomes MAIN, and the application's own
    // bar -- the last bar installed that was never tied to a window -- whenever
    // any other window does. Null `hostView` unties it. The bar on screen is
    // re-decided at once, against the current main window, because building a
    // bar installs it (icoreCreateSystemMenuBar) and a tool window's bar must
    // not stay up over the editor until the next window switch.
    //
    // ⚠ -windowDidBecomeMain:, NOT -windowDidBecomeKey:. A popup refuses main
    // (ICoreAppKitWindow.mm, canBecomeMainWindow) but may take the keyboard, and
    // an ICoreMenu dropped from a tool window must not swap the tool window's
    // menus out while it is open.
    //
    // ⚠ THE HOST IS A VIEW, AND ITS WINDOW IS ASKED AT SWITCH TIME: a widget
    // promoted to a top level is in no window until it is first shown.
    static void scopeToWindow(void* menuBar, void* hostView);

    // A top-level menu. Returns the NSMenu to fill, not the NSMenuItem holding
    // it -- the caller fills menus, and the item is bookkeeping.
    [[nodiscard]] static void* addTopLevelMenu(void* menuBar, const std::string& title);

    // A row. `chord` may be 0 for no shortcut; a chord that is not safe as a key
    // equivalent is DRAWN but not installed, and installAsKeyEquivalent tells
    // the caller which happened so it can register the other half itself.
    //
    // ⚠ A ROW WHOSE TEXT CARRIES A ROLE IS RELOCATED, not added to `menu`. It
    // goes to the application menu, and the handle returned is the row THERE --
    // so a caller that keeps the handle to enable, tick or rename it goes on
    // working. This is what stops an app's "Exit ⌘Q" colliding with the
    // application menu's own Quit, which is the collision this application
    // really has (ICorePrimaryWindow declares Exit on Ctrl+Q).
    [[nodiscard]] static void* addItem(void* menu, const std::string& text,
                                       std::uint32_t chord,
                                       std::function<void()> onActivated,
                                       bool* outInstalledAsKeyEquivalent = nullptr);

    static void* addSeparator(void* menu);

    // The row already carrying this chord's key equivalent anywhere in `menuBar`,
    // or null. Submenus are walked.
    //
    // ⚠⚠ A DUPLICATE KEY EQUIVALENT IS THE QUIETEST FAILURE THIS CLASS HAS.
    // AppKit answers the FIRST row holding a given equivalent and the second is
    // dead -- it still draws its shortcut in the menu, so the app looks correct
    // and one command simply never runs. addItem() therefore REFUSES to install
    // a duplicate and reports it through outInstalledAsKeyEquivalent, leaving
    // the caller to put the chord on the registry instead. This function is how
    // a caller (or an audit) asks the question directly.
    [[nodiscard]] static void* itemWithKeyEquivalent(void* menuBar, std::uint32_t chord);

    // A group title. macOS has no caption row, so this is a disabled item --
    // which is what the platform's own menus use and what Qt's addSection()
    // produces here.
    [[nodiscard]] static void* addCaption(void* menu, const std::string& text);

    // A nested menu, returned ready to fill. Makes the row that opens it too.
    [[nodiscard]] static void* addSubMenu(void* menu, const std::string& text);

    // The same, on a row that ALREADY EXISTS: `item` becomes the row that opens
    // the returned menu, instead of a second row being added beside it.
    //
    // ⚠ IT EXISTS BECAUSE THE WRAPPER ABOVE BUILDS THE ROW FIRST AND DECIDES IT
    // OPENS A MENU SECOND. ICoreMenu::addSubMenu() adds a row for the text and
    // then turns it into a submenu holder -- which is precisely what a QAction
    // holding a QMenu is on the other backend. Reaching for addSubMenu() there
    // would leave the first row in place and add a second one for the same
    // text: a menu with every submenu listed twice, once dead. Measured shape,
    // not a hypothetical -- it is what the first version of ICoreMenuMirror.mm
    // did before this function existed.
    //
    // `item` must already be in `menu`; the menu it is given is titled `text`,
    // because AppKit draws the SUBMENU's title in the strip and ignores the
    // holder's.
    [[nodiscard]] static void* attachSubMenu(void* menu, void* item, const std::string& text);

    // ---- keeping a row in step with its painted twin -----------------------
    static void setItemText(void* item, const std::string& text);
    static void setItemEnabled(void* item, bool enabled);
    static void setItemCheckable(void* item, bool checkable);
    static void setItemChecked(void* item, bool checked);
    [[nodiscard]] static bool isItemEnabled(void* item);
    [[nodiscard]] static bool isItemChecked(void* item);
    [[nodiscard]] static std::string itemText(void* item);
    [[nodiscard]] static std::string itemKeyEquivalent(void* item);
    [[nodiscard]] static unsigned long long itemKeyEquivalentModifierMask(void* item);

    static void clearMenu(void* menu);
    [[nodiscard]] static int itemCount(void* menu);
    [[nodiscard]] static int topLevelCount(void* menuBar);

    // What the application menu's Quit row runs. AppKit's own -terminate: by
    // default, which tears the process down without asking anyone.
    //
    // ⚠ THE APPLICATION MENU IS WHERE A MAC USER QUITS, AND IT IS NOT THE APP'S
    // OWN "Exit" ROW. An app that declares Exit on its File menu has TWO quit
    // paths on this platform, and the one carrying ⌘Q is whichever AppKit finds
    // first -- the application menu, because it is slot 0. So an Exit row's
    // shutdown work is skipped unless the same work is ALSO on this hook.
    // Setting it is how the two paths become one command.
    //
    // The seat cannot reach that work itself: it lives above this module and
    // ICoreEssentials never depends on the SDK (rule 1). Hence a hook.
    static void setQuitHandler(std::function<void()> onQuit);

    // Run just before `menu` is displayed -- where a menu refreshes what its
    // rows say and which are enabled, so it never opens on stale state. The
    // hook may REBUILD the menu: this application's project menu clears and
    // refills itself from here on every open.
    //
    // On this backend it is an NSMenu delegate's -menuNeedsUpdate:, the message
    // AppKit sends before it displays a menu.
    static void setMenuAboutToShowHook(void* menu, std::function<void()> onAboutToShow);

    // Run just after `menu` closes, however it closed -- a row ran, Esc, a
    // click outside. This is what drives `ICoreMenu::onMenuClosed`, which the
    // menu bar needs in order to un-press the title that opened it.
    static void setMenuClosedHook(void* menu, std::function<void()> onClosed);

    // Whether `menu` is on screen right now, tracked from -menuWillOpen: and
    // -menuDidClose:.
    //
    // ⚠ NSMenu HAS NO "am I showing" PROPERTY, so this is bookkeeping rather
    // than a question asked of AppKit -- it is only as good as the delegate
    // messages, and a menu this class did not give a delegate always answers
    // false.
    [[nodiscard]] static bool isMenuOpen(void* menu);

    // Sends that message now, without displaying anything.
    //
    // ⚠ `[NSMenu update]` DOES NOT CALL THE DELEGATE. Measured, 2026-08-20, in
    // every combination of autoenablesItems: -menuNeedsUpdate: fires 0 times
    // for `[m update]` and once for a direct send. This function used to be
    // documented as "what [NSMenu update] calls", which was an untested claim
    // about an API -- the same shape as §0.24's ARC sentence, and wrong the
    // same way. It sends the message itself.
    //
    // ⚠ SO THIS IS A STAND-IN FOR DISPLAY, NOT THE DISPLAY PATH. Displaying a
    // menu needs a window server, which no suite here has. What is proved by
    // driving it this way is everything on THIS side of the message -- that the
    // hook runs, that it may rebuild its own menu, that the delegate is still
    // alive. That AppKit really sends -menuNeedsUpdate: before display is
    // AppKit's documented contract and is NOT proved here.
    static void updateMenu(void* menu);

    // ---- the stand-down guard ----------------------------------------------

    // Asked before a row's callback runs. True means "the caret is in a text
    // entry", and the row stands down -- the same reading the Qt backend's
    // runFromNative() makes. Unset means never stand down.
    //
    // ⚠ A CLICK IS NOT A KEYSTROKE, AND THE GUARD MUST NOT TREAT THEM ALIKE.
    // The guard exists because a KEY EQUIVALENT is answered before the focused
    // field sees the key, so the field has the better claim on it. A user who
    // opens a menu and clicks a row has made no such ambiguous gesture -- they
    // have said exactly which command they want, and standing it down means a
    // menu that does nothing while a text field happens to hold focus. The Qt
    // backend draws the same line with its just-closed flag; this seat draws it
    // from the menu's own -menuDidClose:.
    static void setTextEntryGuard(std::function<bool()> guard);

    // The default guard: true when the key window's first responder is a text
    // view that is actually editable. Installed by createMenuBar().
    [[nodiscard]] static bool defaultTextEntryGuard();

    // ---- driving it without a window server --------------------------------

    // [menu performKeyEquivalent:] against a synthesized NSEvent*. Returns true
    // if a row MATCHED. This is how the suite proves the key-equivalent half
    // without typing and without a bar on screen.
    //
    // ⚠⚠ "MATCHED" IS NOT "RAN", AND THE TWO COME APART IN TWO DIFFERENT WAYS.
    // A matched row that is DISABLED reports true and does not run; and with no
    // application object (see hasApplicationObject) EVERY matched row reports
    // true and does not run. Both are AppKit's behaviour, measured, not this
    // class's choice -- but a caller that reads the BOOL as "the key was
    // handled" will swallow keys in both cases.
    static bool performKeyEquivalent(void* menuBar, void* nsEvent);

    // Runs a row exactly as clicking it does.
    static bool activateItem(void* item);

    // What -menuWillOpen: and -menuDidClose: do, callable directly. A real
    // click is always
    // preceded by its menu closing; the suite has no menu to open, so it says
    // so explicitly rather than leaving the click/keystroke distinction
    // untested for want of a window server.
    static void notifyMenuClosedForTest();
    static void notifyMenuOpenedForTest(void* menu);
    static void notifyMenuClosedForTest(void* menu);

    // One bar, released -- the peer of createMenuBar() rather than of reset().
    //
    // ⚠ IT EXISTS BECAUSE A DESTRUCTOR MUST NOT CALL reset(). reset() is the
    // suite's between-cases teardown and clears the PROCESS-wide slots as well
    // (the application-menu cache, the text-entry guard, the quit handler), so
    // a wrapper that used it to free its own bar would silently disarm hooks
    // that were never its to touch. This releases the bar and nothing else.
    //
    // Uninstalls it first if it is the one on screen -- a released main menu is
    // a bar AppKit still holds a pointer to -- and drops the application-menu
    // cache if it pointed into this bar, since a role row relocated into a menu
    // that is no longer anywhere is a row nobody can reach.
    static void releaseMenuBar(void* menuBar);

    // Everything this class made, released. The suite calls it between cases.
    static void reset();
};
};