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

API — ICoreEssentials/UI/Backends/AppKit/Windows

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

ICoreAppKitModal.h#

ICoreEssentials/UI/Backends/AppKit/Windows/ICoreAppKitModal.h

ICoreAppKitModal#

ICoreAppKitModal.h:27 · class · nested AlertSpec, BuiltAlert · 7 declaration(s)

The AppKit backend's modal seat: nested modal sessions for a blocking dialog's exec(), and the system alert behind the message and input prompts.

class ICoreAppKitModal {
public:
    // ICoreDialog::DialogCode
    static constexpr int kRejected = 0;
    static constexpr int kAccepted = 1;

    // Blocks until stopModal(). Returns the code stopModal() was given.
    static int runModalForWindow(void* nsWindow);
    static void stopModal(int code);
    [[nodiscard]] static bool isRunningModal();

    // ⚠ ASK THIS, NOT isRunningModal(), BEFORE ENDING A SESSION YOU DID NOT
    // START. A dialog that accepts itself while a SECOND dialog is up in front
    // of it would otherwise stop the inner session and hand the inner exec()
    // the outer dialog's result code -- a wrong answer, not a crash, and one
    // that only appears when two dialogs are stacked.
    [[nodiscard]] static bool isModalWindow(void* nsWindow);

    // The window a view sits in, or null. Every prompt in this tier takes a
    // parent that is a WIDGET, whose seat is a view; a sheet is attached to
    // that view's window. The cast lives here so it is written once.
    [[nodiscard]] static void* windowOfNativeView(void* nsView);

    enum class AlertStyle { Informational, Warning, Critical };

    struct AlertSpec {
        std::string title;
        std::string message;
        // In order. Empty means a single "OK".
        //
        // ⚠ THE FIRST BUTTON IS BOTH THE DEFAULT AND THE RIGHTMOST, and those
        // two facts are welded together by the toolkit -- the order buttons are
        // added in is the order they are laid out RIGHT to left, and the first
        // one added is the one Return activates. Measured: a two-button alert
        // puts buttons[0] at x=118 and buttons[1] at x=0.
        //
        // So a caller cannot choose the visual order and the default button
        // independently, and must not try: this is the platform's own alert,
        // and the affirmative belongs on the right of it.
        std::vector<std::string> buttons;
        AlertStyle style = AlertStyle::Informational;

        // ⚠ ESCAPE IS ASSIGNED FOR YOU ONLY IF THE BUTTON IS TITLED "Cancel",
        // AND THAT IS EXACTLY THE CASE THIS TREE OFTEN DOES NOT USE. Measured
        // across the button sets this application really raises:
        //
        //     Yes | No                     -> Return on Yes, NOTHING on No
        //     Save | Discard | Cancel      -> Return on Save, NOTHING on
        //                                     Discard, Escape on Cancel
        //     <labelled action> | Cancel   -> Return on the action, Escape
        //                                     on Cancel
        //
        // So the leading button always takes Return, a button whose title is
        // literally "Cancel" always takes Escape, and every other dismissive --
        // "No", "Discard" -- takes nothing at all. A Yes/No prompt left to the
        // toolkit therefore has NO way out but answering it, which is a
        // behaviour change from the other backend, where Escape rejects any
        // dialog. Name the dismissive button's index and it gets "\033".
        //
        // ⚠ A CHECK THAT ONLY TRIED "Cancel" WOULD CONCLUDE THIS FIELD IS
        // UNNECESSARY. It is redundant for three of this tree's four prompt
        // shapes and load-bearing for the fourth.
        //
        // -1 means no escape route, which is right for a one-button alert whose
        // only button already dismisses it.
        int escapeButtonIndex = -1;

        // When set, the alert grows a text field and the typed value is
        // returned through `input`.
        bool withTextField = false;
        std::string initialText;

        // When non-empty, the accessory is a POP-UP of these instead of a
        // field, and the chosen entry is returned through `input`. Set
        // `itemsEditable` for a combo the user may also type into.
        std::vector<std::string> items;
        int selectedItem = 0;
        bool itemsEditable = false;

        // Unscaled pixels; 0 leaves the width to the toolkit. An alert sizes
        // itself to its text, so a long body comes up as a tall narrow column
        // unless something wide is put in it.
        int minimumWidth = 0;

        // The window this prompt belongs to, as the window seat's
        // nativeWindow() hands it out. Null for an app-modal prompt.
        //
        // ⚠ A SHEET ON AN OFF-SCREEN WINDOW IS AN UNANSWERABLE PROMPT. The
        // toolkit attaches it happily, blocks, and draws nothing the user can
        // reach -- so a parent is honoured as a sheet only while it is
        // actually visible, and falls back to app-modal otherwise. That guard
        // is the difference between a native-looking prompt and a hang.
        void* parentWindow = nullptr;
    };

    // Returns the INDEX of the button pressed, or -1 if the session was stopped
    // externally. The toolkit's own returns are NSAlertFirstButtonReturn (1000)
    // and up; that encoding does not leave this class.
    //
    // `input` receives the field's text or the pop-up's selected entry when the
    // spec asked for one, whatever button ended the prompt.
    static int runAlert(const AlertSpec& spec, std::string* input = nullptr);

    // What runAlert() would BUILD, without presenting it.
    //
    // ⚠ NOT A CONVENIENCE -- it is the only way most of this class is provable.
    // A prompt cannot be answered by a suite without a person clicking it, and
    // three of the four things that go wrong here are settled before anything
    // is shown: which titles the buttons carry, in which order, and which one
    // Escape reaches. The last of those is invisible from outside and is the
    // one the toolkit does NOT do for you.
    struct BuiltAlert {
        std::vector<std::string> buttonTitles;      // in the toolkit's own order
        std::vector<std::string> keyEquivalents;    // "" for most, "\033" for Escape
        int accessoryItemCount = -1;                // -1 when there is no list
        double accessoryWidth = 0.0;
        bool hasTextField = false;
    };
    static BuiltAlert describeForTest(const AlertSpec& spec);
};
};

ICoreAppKitNativeDialogs.h#

ICoreEssentials/UI/Backends/AppKit/Windows/ICoreAppKitNativeDialogs.h

The AppKit backend's seat for the three pickers the application hands to the operating system, plus the reveal-in-Finder helper. (The planning row is named in the .mm, not here -- the docs generator lifts header banners into public API pages.)

NO OBJECTIVE-C IN THIS HEADER. Panels and parents cross as opaque pointers and everything else is a plain string, so the verify test drives the whole parsing half without AppKit.

⚠ THE HARD PART IS THE FILTER STRING, NOT THE PANEL. Qt states a file filter as "Images (*.png *.jpg);;All Files (*)" -- a display label and a glob list per filter, filters separated by ;;. macOS states the same idea as a list of UNIFORM TYPE IDENTIFIERS, which have no labels and no globs. So the two halves of this file are very different kinds of code: the parse is pure

ICoreAppKitFileFilter#

ICoreAppKitNativeDialogs.h:27 · struct · 0 declaration(s)

One Name (patterns) group out of a Qt filter string.

struct ICoreAppKitFileFilter {
public:
    // What the user reads. "Images" out of "Images (*.png *.jpg)".
    std::string label;

    // Bare extensions, lowercased, with no dot and no star: {"png", "jpg"}.
    //
    // ⚠ EMPTY MEANS "ANY FILE", and it is a real case rather than a
    // degenerate one: `"All Files (*)"` is in this application's corpus, and so
    // is `"Executables (*.exe *)"` -- a named filter whose pattern list
    // CONTAINS a bare star, which makes the whole group match anything and the
    // `exe` beside it decorative. Treating that group as "only .exe" would hide
    // every file the user wanted to pick, on a platform where `.exe` means
    // nothing anyway.
    std::vector<std::string> extensions;
};
};

ICoreAppKitPrompts.h#

ICoreEssentials/UI/Backends/AppKit/Windows/ICoreAppKitPrompts.h

Every decision the message and input prompts make, as PLAIN C++ -- no toolkit, no Objective-C, nothing that needs a window server to evaluate. The seat beside this file presents the record these functions build and reports which button ended it; the two functions at the bottom turn that index back into the answer the wrapper promised its caller.

⚠ THIS SPLIT IS THE POINT OF THE FILE, not tidiness. A prompt cannot be answered by a test without a person clicking it, so a suite over the PRESENTATION proves almost nothing -- while the button set, their order, the default, the escape route and the index-to-answer mapping are exactly where a wrong answer comes from, and every one of them is decidable here. Getting the mapping wrong turns "Discard" into "Save"; getting the escape index wrong leaves a prompt that cannot be dismissed. Neither shows up in a compile and neither is visible in a screenshot.

File-scope declarations#

// ---- the six message-box shapes -------------------------------------------
enum class Notification { Critical, Warning, Information };

// Mirrors ICoreMessageBox::SaveAnswer without naming it -- this header sits
// under the backend and does not include the public one.
enum class SaveChoice { Save, Discard, Cancel };

ICoreAppKitShortcuts.h#

ICoreEssentials/UI/Backends/AppKit/Windows/ICoreAppKitShortcuts.h

ICoreAppKitShortcuts#

ICoreAppKitShortcuts.h:26 · class · 11 declaration(s)

The AppKit backend's shortcut registry: ICoreShortcut's seat.

class ICoreAppKitShortcuts {
public:
    // `scope` takes ICoreShortcutScope's values, which are pinned to Qt's
    // ShortcutContext: Window = 1, Application = 2, WidgetWithChildren = 3.
    //
    // `hostView` is an NSView* (as ICoreAppKitView::nativeView() hands out).
    // It may be null only for Application scope; the other two resolve against
    // it and a null host can never match.
    static int add(ICoreKey key,
                   ICoreKeyModifiers modifiers,
                   void* hostView,
                   int scope,
                   std::function<void()> onActivated);

    // ⚠⚠ CONSULTED BEFORE THE KEY IS CLAIMED, WHICH IS THE WHOLE POINT AND IS
    // NOT WHAT A CALLBACK THAT RETURNS EARLY CAN DO. handleKeyEvent() swallows
    // the keystroke the moment an entry matches, so a shortcut that decided --
    // inside its own callback -- that it should not act had ALREADY eaten the
    // key by the time it decided. `ICoreMenu` registers exactly that shape: the
    // Edit menu's Delete row also carries Backspace, and both callbacks stand
    // down while a text entry holds the focus.
    //
    // What that cost, and it is the reason this exists: Delete and Backspace
    // matched, ran a callback that correctly did nothing, and were swallowed --
    // so NO text field, code editor or renamed block on this backend could
    // delete a character. The guard was right, its position was not.
    //
    // An entry whose predicate answers false is skipped exactly as a disabled
    // one is: the scan carries on to the next entry, and a keystroke nobody
    // claims reaches the focused view. Unset means always eligible.
    static void setEligibility(int id, std::function<bool()> isEligible);

    static void remove(int id);
    static void setEnabled(int id, bool enabled);
    [[nodiscard]] static bool isEnabled(int id);
    [[nodiscard]] static int count();
    static void removeAll();

    // Dispatches one NSEvent* against the registry. Returns true if a shortcut
    // matched and was invoked, which is what the monitor uses to swallow the
    // event. Public so the suite can drive it without a window server.
    static bool handleKeyEvent(void* nsEvent);

    // +addLocalMonitorForEventsMatchingMask:. Idempotent.
    //
    // ⚠ add() CALLS THIS ITSELF, so a registry holding an entry is always
    // listening. It stayed public for explicit control and for the suite, but
    // it is no longer something a caller can forget: it had ZERO production
    // callers until A3.5 measured it, which meant every shortcut in a shipping
    // appkit build was registered and never fired, with every check green.
    static void installMonitor();
    static void removeMonitor();
    [[nodiscard]] static bool isMonitorInstalled();
};
};

ICoreAppKitTitleBarSeat.h#

ICoreEssentials/UI/Backends/AppKit/Windows/ICoreAppKitTitleBarSeat.h

ICoreAppKitTitleBarSeat#

ICoreAppKitTitleBarSeat.h:18 · class · bases public ICoreTitleBarHost · pImpl · 13 declaration(s)

The AppKit seat for ICoreTitleBar, shared by ICoreWindow and ICoreDialog.

class ICoreAppKitTitleBarSeat : public ICoreTitleBarHost {
public:
    ICoreAppKitTitleBarSeat(ICoreAppKitWindow& window, ICoreTitleBarRole role);
    ~ICoreAppKitTitleBarSeat() override;
    ICoreAppKitTitleBarSeat(const ICoreAppKitTitleBarSeat&) = delete;
    ICoreAppKitTitleBarSeat& operator=(const ICoreAppKitTitleBarSeat&) = delete;

    // The explicit choice; nullptr keeps the system titlebar. Either opts out
    // of ICoreTitleBarProvider.
    void setBar(ICoreTitleBar* bar);
    [[nodiscard]] ICoreTitleBar* bar() const;

    // Ask ICoreTitleBarProvider once, on the first show of a decorated window.
    void adoptDefaultIfNeeded();

    void titleBarBeginMove() override;
    void titleBarDoubleClicked() override;
    void titleBarMinimize() override;
    void titleBarToggleMaximize() override;
    void titleBarClose() override;
    void titleBarChanged() override;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreAppKitWindow.h#

ICoreEssentials/UI/Backends/AppKit/Windows/ICoreAppKitWindow.h

ICoreAppKitWindow#

ICoreAppKitWindow.h:48 · class · pImpl · 63 declaration(s)

class ICoreAppKitWindow {
public:
    ICoreAppKitWindow();
    ~ICoreAppKitWindow();
    ICoreAppKitWindow(const ICoreAppKitWindow&) = delete;
    ICoreAppKitWindow& operator=(const ICoreAppKitWindow&) = delete;

    // A3.4. Applies the style mask, window level, shadow and focus behaviour in
    // one place. Safe to call more than once; the NSWindow is reconfigured, not
    // rebuilt, so a content view already installed survives.
    void setNature(ICoreAppKitWindowNature nature);
    [[nodiscard]] ICoreAppKitWindowNature nature() const;

    // What AppKit will actually allow, after the override. The test asserts
    // this rather than the style mask, because the mask is the input and this
    // is the behaviour callers depend on.
    [[nodiscard]] bool canBecomeKey() const;
    [[nodiscard]] bool hasShadow() const;
    [[nodiscard]] bool hidesOnDeactivate() const;
    [[nodiscard]] int  level() const;

    // The NSWindow, for the one wrapper that owns this seat.
    [[nodiscard]] void* nativeWindow() const;

    // Takes the NSView* an ICoreAppKitView hands out through nativeView().
    void setContentView(void* nsView);
    [[nodiscard]] void* contentView() const;

    void setTitle(const std::string& utf8);
    [[nodiscard]] std::string title() const;

    // TOP-LEFT, y-DOWN. See the banner.
    void setFrame(double x, double y, double width, double height);
    void frame(double& x, double& y, double& width, double& height) const;
    void resize(double width, double height);
    void move(double x, double y);
    void setMinimumSize(double width, double height);
    void minimumSize(double& width, double& height) const;

    // ⚠⚠ THE CONTENT HALF, AND IT IS NOT A CONVENIENCE -- IT IS WHERE THE
    // OTHER TOOLKIT'S RESIZE LANDS. `QWidget::resize` on a top level sizes the
    // CONTENT area on every platform; `-[NSWindow setFrame:]` -- which
    // setFrame() and resize() above go to -- sizes the OUTER rectangle, chrome
    // included. So `ICoreWindow::resize(520, 360)` gave 520x360 of content on
    // the shipping backend and 520x328 here, a title bar short, with the
    // window's whole layout run inside the smaller rectangle. Found by running
    // the application rather than by any suite (A1.3h, harness_app).
    //
    // ⚠ THE SPLIT IS DELIBERATE AND THE SEAT KEEPS ITS FRAME MEANING: `move`,
    // `moveToScreenCenter` and `saveGeometry` all want the outer rectangle, and
    // appkit_window's own checks document setFrame/resize as frame operations.
    // The WRAPPER is where Qt parity is defined, so the wrapper calls these.
    //
    // ⚠ A BORDERLESS WINDOW HAS NO CHROME and these degenerate to the frame
    // ones exactly -- which matters, because every popup and menu on this
    // backend is borderless.
    void setContentSize(double width, double height);
    void contentSize(double& width, double& height) const;

    // The same two constraints, expressed in content units, for the same
    // reason: `QWidget::setMinimumSize` on a top level bounds the content.
    // Storing them frame-side keeps setFrame()'s clamp -- and appkit_window's
    // checks on it -- exactly as they are.
    void setMinimumContentSize(double width, double height);
    void minimumContentSize(double& width, double& height) const;
    void setMaximumContentSize(double width, double height);
    void maximumContentSize(double& width, double& height) const;

    // The other half of a fixed size. Unset means unbounded, which is not the
    // same as 0x0 -- hasMaximumSize() is what tells the two apart, and a
    // caller that reads maximumSize() without asking is told a window with no
    // maximum is pinned to nothing.
    void setMaximumSize(double width, double height);
    void maximumSize(double& width, double& height) const;
    [[nodiscard]] bool hasMaximumSize() const;

    void show();
    void hide();
    void raise();
    void activate();
    void close();

    [[nodiscard]] bool isVisible() const;
    [[nodiscard]] bool isMinimized() const;
    [[nodiscard]] bool isActive() const;

    void setOpacity(double opacity);
    [[nodiscard]] double opacity() const;

    // -[NSWindow backingScaleFactor], live: it changes when the window moves
    // between displays, so this asks every time rather than caching.
    [[nodiscard]] double devicePixelRatio() const;

    // windowShouldClose: -> this hook. Returning without calling close() is
    // what lets a caller veto, which is the whole point of the seam.
    void setCloseRequestedHook(std::function<void()> hook);
    void setActivatedHook(std::function<void()> hook);
    void setResizedHook(std::function<void(double width, double height)> hook);
    void setScreenChangedHook(std::function<void(double devicePixelRatio)> hook);

    // --- the custom caption (W10.125, A10.72) ------------------------------
    //
    // ⚠ THE BAR GOES INTO THE TITLEBAR, NOT OVER THE CONTENT. The documented
    // alternative -- NSWindowStyleMaskFullSizeContentView -- slides the content
    // view up under the titlebar, which changes what contentSize(), every
    // min/max content size and every child frame in the window mean. This
    // seat instead adds `nsView` to the titlebar's own container view, behind
    // the traffic lights, and makes the titlebar transparent with its title
    // hidden: the content view is not touched at all, so nothing below the
    // strip moves.
    //
    // THE HEIGHT IS APPKIT'S, CHOSEN FROM `preferredHeight`. AppKit centres the
    // traffic lights for exactly three titlebar heights -- 28 (plain), 38
    // (an empty toolbar, unified compact) and 52 (unified) -- so the nearest
    // one at or below the preference is used and captionHeight() says which.
    // The content size is kept across the change.
    //
    // Null takes the view out and puts the platform's own titlebar back.
    void setCaptionView(void* nsView, double preferredHeight);
    [[nodiscard]] double captionHeight() const;

    // Where the traffic lights end, measured from the titlebar's left edge.
    // Zero when they are not there (full screen, or no caption view).
    [[nodiscard]] double captionLeftInset() const;

    // Runs on activation (both edges), resize, title change and the full-screen
    // transitions. One slot.
    void setCaptionStateHook(std::function<void()> hook);

    // Start the system's window drag from the mouse-down being handled now.
    void beginCaptionMove();
    // Do what the user's "double-click a window's title bar to" setting says.
    void captionDoubleClicked();
    void minimize();
    void toggleZoom();
    [[nodiscard]] bool isZoomed() const;

    void moveToScreenCenter();
    void moveToScreenBottomRightCorner(double paddingX, double paddingY);

    // ⚠⚠ THE PLACEMENT THE OTHER TOOLKIT MAKES FOR FREE. Called by show(),
    // raise() and activate(); centres a DecoratedWindow or FramelessWindow that
    // nobody has positioned, the first time it goes on screen, and does nothing
    // ever again. Every window here is constructed at (0, 0) and every sizing
    // verb keeps the origin it has, so without this a tool window opens in the
    // display's top-left corner -- which is what the owner reported. Public
    // rather than private because the header surface rule leaves a class no
    // private lines but the pImpl residue; the .mm carries the full account.
    void placeOnFirstShow();

    // "Somebody has said where this window goes" -- what stands placeOnFirstShow()
    // down. move() and the two moveToScreen* verbs record it themselves; this is
    // for a caller that places a window some other way, such as
    // ICoreWindow::restoreGeometry() writing a remembered frame.
    void markPositioned();

    // --- screens, in the same top-left y-down convention -------------------
    [[nodiscard]] static int screenCount();
    // Whole screen, including the menu bar and the Dock.
    static bool screenFrame(int index, double& x, double& y, double& width, double& height);
    // What a maximised window may occupy: menu bar and Dock removed.
    static bool screenVisibleFrame(int index, double& x, double& y, double& width, double& height);
    [[nodiscard]] static double screenDevicePixelRatio(int index);
    // The screen the window currently sits on, or -1 when it is off-screen.
    [[nodiscard]] int screenIndex() const;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

File-scope declarations#

// The AppKit backend's window seat: one NSWindow, its delegate, and the screen
// queries that go with it.
// 
// NO OBJECTIVE-C AND NO TOOLKIT TYPE IN THIS HEADER -- the rule the event map,
// the painter and the view base beside it all follow. The NSWindow leaves
// through nativeWindow() as an opaque pointer and is cast only in the .mm.
enum class ICoreAppKitWindowNature {
    DecoratedWindow,   // Qt::Window                      -- titled, the ordinary case
    FramelessWindow,   // Qt::Window | Frameless          -- chrome gone, focus kept
    Popup,             // Qt::Popup  | Frameless          -- menu level, hides on deactivate
    FloatingCard,      // Qt::Tool   | Frameless | NoDropShadow | DoesNotAcceptFocus
};

ICoreAppKitWindowRegistry.h#

ICoreEssentials/UI/Backends/AppKit/Windows/ICoreAppKitWindowRegistry.h

Which ICoreWindow shell hosts a given NSWindow. The AppKit answer to the question ICoreWindowAccess.h answers on the Qt backend.

IMPLEMENTATION SIDE ONLY -- include this from a .mm inside UI/Backends/AppKit/.

⚠ WHY A REGISTRY AND NOT A PROPERTY. The Qt seat writes a dynamic QObject property, _icoreWindowShell, onto the Impl and reads it back. That works because the Impl IS the QMainWindow, so the property lives exactly as long as the thing it describes. AppKit's nearest equivalent is an associated object on the NSWindow, which would work -- and would put a strong-ish back-reference from a toolkit object into a C++ object whose destructor is the only thing that can clear it. A window outliving its shell (which AppKit permits: the toolkit may hold it while an animation finishes) would

Declares no class of its own — see the file.