API — ICoreBlocks/Shell
The public contract of 4 header(s) under src/ICoreBlocks/Shell — 4 class/struct definition(s), 111 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.
| Header | Defines | Declarations | Bases |
|---|---|---|---|
EditorWindowImpl.h | EditorWindow | 18 | :Impl |
ICoreAPIs.h | ICoreAPIs | 75 | — |
ICoreOtherObjectsRegistry.h | ICoreOtherObjectsRegistry | 2 | — |
Initialization.h | Initialization | 16 | — |
EditorWindowImpl.h#
src/ICoreBlocks/Shell/EditorWindowImpl.h
EditorWindowImpl.h -- the definition of icore::EditorWindow::Impl.
INTERNAL. Under src/, and it stays there: it names ICorePrimaryWindow, and include/icore/ is toolkit-free by contract (DEVELOPER_GUIDELINES.md). Nothing under include/ may include this file, and tools/check_sdk_boundary.sh is what notices if that ever changes.
Why a header at all, when the whole point of Impl is that it lives in one translation unit:
Application::createEditorWindow() has to CONSTRUCT an Impl and hand it to an EditorWindow built through EditorWindow's private constructor. Only Application may call that constructor -- it is EditorWindow's sole friend -- so the factory cannot be a free function in EditorWindow.cpp. And a
EditorWindow#
EditorWindowImpl.h:44 · class · bases :Impl · 18 declaration(s)
class EditorWindow : :Impl {
public:
// Builds the widget, hidden. Throws whatever the widget's construction
// throws; there is no failed-but-constructed state, which is what lets
// createEditorWindow() promise a never-null handle.
//
// @pre Initialization::initializeServices() has run. ICorePrimaryWindow's
// constructor reaches for the theme manager, the studio window
// registry, the studio registry and the subsystem tree node registry.
// icore::Application's Impl is what guarantees this.
Impl();
~Impl();
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
Impl(Impl&&) = delete;
Impl& operator=(Impl&&) = delete;
// --- what the public surface forwards to -------------------------------
void setTitle(std::string_view title);
void show();
// Takes the window off screen if it is on it. The startup hold's
// (Application::holdWindowsUntilReady): nothing may show the window
// before the ready report does.
void hideIfShown();
void close();
bool openProject(std::string_view utf8Path);
// The CALLER's close hook. One hook, replaced rather than accumulated --
// the public signature returns no subscription token, so there would be no
// way to detach one of several.
void onCloseRequested(std::function<void()> fn);
// --- lifetime bookkeeping ----------------------------------------------
// The SDK's OWN close hook, kept in a separate slot from the caller's.
//
// Application::createEditorWindow() installs one here to notice that a
// window has left the screen, which is what ends the session when the last
// one goes. If it shared the slot above, a caller doing the perfectly
// ordinary thing -- asking to be told when its window closes -- would
// silently overwrite it, and the application would stop quitting when its
// last window was closed. Two slots, both fired, neither able to displace
// the other.
void setInternalCloseHook(std::function<void()> fn);
// Run from ~Impl. Application::createEditorWindow() installs one to strike
// the window off the Application's live-window list, which is the list
// ~Application::Impl asserts is empty.
void setDestroyedHook(std::function<void()> fn);
// --- the Impl's own state ----------------------------------------------
//
// Public, and deliberately so. This class IS the Impl the header surface
// rule moves state INTO -- the rule's own recipe spells one
// `class ICoreFoo::Impl { public: /* every field */ };`
// (DEVELOPER_GUIDELINES.md). It is reachable from exactly two translation
// units, both listed at the top of this file, and `private:` here bought
// nothing: the only code that could have been kept out is EditorWindow's
// own, which owns it.
// Fires both close hooks, internal first. Installed on the window as its
// close-requested hook (ICoreWindow::setCloseRequestedHook), which is the
// Qt-free replacement for the QObject event filter this class used to
// carry -- the reason EditorWindow.cpp names no toolkit type at all now.
void notifyCloseRequested();
std::unique_ptr<ICorePrimaryWindow> m_window;
std::function<void()> m_onCloseInternal;
std::function<void()> m_onCloseRequested;
std::function<void()> m_onDestroyed;
};
};
ICoreAPIs.h#
src/ICoreBlocks/Shell/ICoreAPIs.h
Explicit-model forms of the three above, for callers not working on the focused canvas (mirrors the moveObjectsToTrash / permanentlyDeleteObjects convention).
ICoreAPIs#
ICoreAPIs.h:19 · class · 75 declaration(s)
class ICoreAPIs {
public:
static void closeSoftware();
static void forceCloseSoftware();
static void migrateSelection(ICoreSubsystemTreeNode* destination, const ICorePoint& positionOffset);
static void cutSelection();
static void copySelection();
static void paste(ICoreSubsystemTreeNode* destination, const ICorePoint& newPosOnCanvas);
// Explicit-model forms of the three above, for callers not working on the focused canvas
// (mirrors the moveObjectsToTrash / permanentlyDeleteObjects convention).
static void cutSelection(ICoreCanvasSelectionModel* selectionModel);
static void copySelection(ICoreCanvasSelectionModel* selectionModel);
static void migrateObjects(ICoreCanvasSelectionModel* selectionModel, ICoreSubsystemTreeNode* destination, const ICorePoint& positionOffset);
static void moveSelectionToTrash();
static void moveObjectsToTrash(ICoreCanvasSelectionModel* selectionModel);
static void permanentlyDeleteObjects(ICoreCanvasSelectionModel *selectionModel);
static void permanentlyDeleteSelectedObjects();
// Both return the created subsystem's tree node, or nullptr when creation failed
// (invalid parent path, name collision, ...). Callers may ignore the return.
static ICoreSubsystemTreeNode* createNewEmptySubsystem(const std::string& parentPath);
static ICoreSubsystemTreeNode* createSubsystemAndMigrateSelection();
// Explicit form: arbitrary parent path and selection model, plus the position the new
// subsystem block takes when the selection is empty (the no-arg overload leaves it to
// the backend default).
static ICoreSubsystemTreeNode* createSubsystemAndMigrateSelection(const std::string& parentPath, ICoreCanvasSelectionModel* selectionModel, const ICorePoint& subsystemPosIfSelectionEmpty);
// Re-positions the blocks of `treeNode` into a clean left-to-right layered diagram,
// ordered by execution (solver) order. No-op for a null node.
static void autoArrange(ICoreSubsystemTreeNode* treeNode);
// Series-reduces the two selected SISO blocks into one State Space block. See
// ICoreSeriesReduction::reduceSeriesSelection for the exact preconditions; logs and leaves the
// model untouched if the current selection doesn't satisfy them.
static bool reduceSeriesSelection(ICoreCanvasSelectionModel* selectionModel);
// Parallel-reduces the two selected SISO blocks (same input source, outputs summed by a shared
// Sum block) into one State Space block. See ICoreParallelReduction::reduceParallelSelection for
// the exact preconditions; logs and leaves the model untouched if the current selection doesn't
// satisfy them.
static bool reduceParallelSelection(ICoreCanvasSelectionModel* selectionModel);
// Reduces the selected plant/feedback/Sum-block closed loop into one State Space block. See
// ICoreFeedbackReduction::reduceFeedbackSelection for the exact preconditions; logs and leaves
// the model untouched if the current selection doesn't satisfy them.
static bool reduceFeedbackSelection(ICoreCanvasSelectionModel* selectionModel);
// Auto reduction over `treeNode` (only the selected blocks, when two or more are
// selected): one step tries a series reduction, then parallel, then feedback, and
// applies the first that passes; full repeats until none is left. Every reduction is
// verified whatever the preference says, and the run is one undo step. Returns how
// many reductions were applied. See ICoreAutoReduction.
static int autoReduceOneStep(ICoreSubsystemTreeNode* treeNode, ICoreCanvasSelectionModel* selectionModel);
static int autoReduceFully(ICoreSubsystemTreeNode* treeNode, ICoreCanvasSelectionModel* selectionModel);
// Shows/hides the name label of every selected block. Per-block model state, so it is
// captured in the recipe and survives undo/redo and a save/reload. See ICoreBlockRename.
static void setSelectionNameLabelsVisible(ICoreCanvasSelectionModel* selectionModel, bool visible);
// Hides the labels when any selected block still shows one, shows them all otherwise.
static void toggleSelectionNameLabels(ICoreCanvasSelectionModel* selectionModel);
// Saves the whole project to disk immediately (the Ctrl+S write path).
static void saveProject();
// Discards the in-memory model and reloads the project from what is on disk.
static void reloadProjectFromDisk();
static void undo();
static void redo();
// A console / script recipe statement just ran and may have edited the diagram
// without logging. See ICoreStudioStateMachine::notePossibleUnloggedEdit().
static void notePossibleUnloggedDiagramEdit();
// ICoreStudioStateMachine's logging switch, for layers that may not name it:
// a caller that turns it off reads it first and restores only what it found.
static bool isLoggingDiagramEdits();
static void setLoggingDiagramEdits(bool enabled);
// Builds the project. On failure, raises the "Build Failed" warning notification
// (details go to the run diagnosis panel) and returns false.
static bool buildProject();
static void runProjectSimulator();
static void runProjectSimulatorInDebugMode();
static void stepProjectSimulator();
static void pauseProjectSimulator();
static void stopProjectSimulator();
// Comments out (or uncomments) every selected block. Per-block model state, so it is
// captured in the recipe and survives undo/redo and a save/reload. See ICoreBlockComment.
static void setSelectionCommentedOut(ICoreCanvasSelectionModel* selectionModel, bool commentedOut);
// Uncomments when every selected block is commented out, comments all out otherwise.
static void toggleSelectionCommentedOut(ICoreCanvasSelectionModel* selectionModel);
// Turns every selected block by `degrees`, clockwise on screen. One undo step. See ICoreBlockRotate.
static void rotateSelection(ICoreCanvasSelectionModel* selectionModel, double degrees);
// Opens the one selected block's name for typing; false when there is not exactly one
// block selected with its name showing. See ICoreBlockRename.
static bool beginRenamingSelection(ICoreCanvasSelectionModel* selectionModel);
// Clones the selected objects (blocks, links, areas, notes, images) into `destination`,
// re-establishes the clones' connections among themselves, and applies `positionOffset`.
// See ICoreObjectsDuplication.
static void duplicateSelection(ICoreCanvasSelectionModel* selectionModel, ICoreSubsystemTreeNode* destination, const ICorePoint& positionOffset);
// Sets / scales the focused canvas's zoom. Clamping happens in the canvas parent.
static void setFocusedCanvasZoom(double zoomLevel);
static void zoomFocusedCanvasBy(double factor);
// Focused-tab navigation. Both no-op quietly when there is nowhere to go.
static void navigateBack();
static void navigateUp();
// Loads the subsystem at `treeNodePath` into the focused tab.
static void navigateToPath(const std::string& treeNodePath);
// Opens the subsystem at `treeNodePath` in a new tab of the focused window, or in a
// new editor window. A tree node lives in exactly one canvas, so if it is already
// loaded somewhere these focus the tab that holds it instead of opening a blank one.
static void openSubsystemInNewTab(const std::string& treeNodePath);
static void openSubsystemInNewWindow(const std::string& treeNodePath);
static void closeFocusedTab();
// Selects every child of the tree node loaded on the focused canvas.
static void selectAllOnFocusedCanvas();
// Releases any in-flight mouse grabs, then restores the focused canvas's pan and
// zoom to their defaults.
static void resetFocusedCanvasPanAndZoom();
// Handle lookups (re-exported from ICoreSubsystemTreeNodeRegistry). Paths accept both
// display and canonical forms; nullptr when nothing matches.
static ICoreSubsystemTreeNode* findTreeNodeByPath(const std::string& path);
static ICoreBlock* findBlockByPath(const std::string& path);
static ICorePort* findPortByPath(const std::string& path);
// Object creation. Each creates into `treeNode` at `position` (canvas scene
// coordinates), logs one undo step, and returns the created object; nullptr and
// no-op for a null node. `type` for blocks is the library path, e.g.
// "Control_Systems/Continuous/State_Space".
static ICoreBlock* createBlock(ICoreSubsystemTreeNode* treeNode, const std::string& type, const ICorePoint& position);
static ICoreCanvasArea* createCanvasArea(ICoreSubsystemTreeNode* treeNode, const ICorePoint& position);
static ICoreCanvasTextBox* createNote(ICoreSubsystemTreeNode* treeNode, const ICorePoint& position);
static ICoreImage* createImage(ICoreSubsystemTreeNode* treeNode, const ICorePoint& position);
// Link wiring (re-exported from ICoreAutoLinkConnector). All validate their inputs,
// log-and-return-null on an invalid connection, and log one undo step on success.
// `linkType` names the link class, e.g. "Signal".
static ICoreLink* generateLink_Port_Port(ICoreSubsystemTreeNode* treeNode, const std::string& linkType, ICorePort* tailPort, ICorePort* headPort);
static ICoreLink* generateLink_Port_Null(ICoreSubsystemTreeNode* treeNode, const std::string& linkType, ICorePort* tailPort, const ICorePoint& headCoordination);
static ICoreLink* generateLink_Null_Port(ICoreSubsystemTreeNode* treeNode, const std::string& linkType, const ICorePoint& tailCoordination, ICorePort* headPort);
static ICoreLink* generateLink_Null_Null(ICoreSubsystemTreeNode* treeNode, const std::string& linkType, const ICorePoint& tailCoordination, const ICorePoint& headCoordination);
static ICoreLinkBranch* generateLinkBranch_Link_Port(ICoreLink* link, ICorePort* headPort);
// Recipe / Simulink-script transfer. Return false on failure; per-line warnings and
// the failure reason go to the transfer Result, which the notification log carries.
static bool importRecipe(ICoreSubsystemTreeNode* node, const std::string& filePath);
static bool exportRecipe(ICoreSubsystemTreeNode* node, const std::string& filePath);
static bool importSimulinkScript(ICoreSubsystemTreeNode* node, const std::string& filePath);
static bool exportSimulinkScript(ICoreSubsystemTreeNode* node, const std::string& filePath);
// MATLAB .m -> .icore: the command bridge's import half, the mirror of
// ICoreMatlabCommandBridge::exportToMatlabScript.
// Unlike the four above this one touches no diagram: it writes a script into
// <project>/scripts, so it takes no node. `scriptName` empty means the .m file's own
// stem. False when the .m could not be read, when nothing in it crossed, or when the
// target exists and `overwrite` is false -- the import never overwrites silently.
// The per-statement refusals are warnings on the bridge Result; call
// ICoreMatlabCommandBridge::importFromMatlabScript directly to read them.
static bool importMatlabScript(const std::string& filePath, const std::string& scriptName,
bool overwrite = false);
// Writes the canvas's diagram to a PDF at `filePath` — no dialog. False on failure.
static bool exportCanvasAsPDF(ICoreCanvas* canvas, const std::string& filePath);
static bool exportFocusedCanvasAsPDF(const std::string& filePath);
// Code deploy (re-exported from ICoreCodeEngine).
static ICoreCodeExportTarget* createCodeExportTarget();
static void deleteCodeExportTarget(const ICoreCodeExportTarget* target);
static bool deployTarget(const ICoreCodeExportTarget* target);
static bool deployAllTargets();
static void cancelDeploy();
};
};
ICoreOtherObjectsRegistry.h#
src/ICoreBlocks/Shell/ICoreOtherObjectsRegistry.h
ICoreOtherObjectsRegistry#
ICoreOtherObjectsRegistry.h:6 · class · 2 declaration(s)
class ICoreOtherObjectsRegistry {
public:
// ----- Global Variables Space (single, app-lifetime) -----
static ICoreVariablesSpace* getGlobalVariablesSpace();
static void clearGlobalVariables();
};
};
Initialization.h#
src/ICoreBlocks/Shell/Initialization.h
Everything the application object itself has to be told: what the app calls itself to the window system, the icon every window is marked with, and the palette all widgets inherit. Runs against an already-constructed application (nothing here can precede it) and before any widget exists, so it belongs between the application's construction and initializeApp().
⚠ TAKES THE APPLICATION, and used to take nothing. The old spelling reached the toolkit's statics directly, which is exactly what kept a QApplication include in this module; the properties it sets are ICoreApplication's now, and they are instance methods there on purpose. The reference is forward-declared, so this header still names no toolkit type.
Initialization#
Initialization.h:12 · class · 16 declaration(s)
class Initialization {
public:
// Everything the application object itself has to be told: what the app
// calls itself to the window system, the icon every window is marked with,
// and the palette all widgets inherit. Runs against an already-constructed
// application (nothing here can precede it) and before any widget exists,
// so it belongs between the application's construction and
// initializeApp().
//
// ⚠ TAKES THE APPLICATION, and used to take nothing. The old spelling
// reached the toolkit's statics directly, which is exactly what kept a
// QApplication include in this module; the properties it sets are
// ICoreApplication's now, and they are instance methods there on purpose.
// The reference is forward-declared, so this header still names no
// toolkit type.
static void configureApplication(ICoreApplication& app);
// Everything an editor window is built on, and nothing that builds one:
// loggers, communications, preferences, theme, the four registries, the
// state machine, and the console command bindings. Split out of
// initializeApp() because ICorePrimaryWindow's constructor reaches for the
// theme manager and three of those registries, so ANY caller that wants a
// primary window has to have run this first -- including
// icore::Application, which is a lifetime scope and creates no window of
// its own (see src/ICoreBlocks/Shell/Application.cpp).
//
// Idempotent: the second and later calls do nothing, so the SDK path and
// the Main.cpp path can both ask for services without either having to
// know whether the other already ran.
//
// `reportStep`, when given, hears what is about to happen and how far
// through startup it is (0..1) -- icore::Application forwards it to a
// startup screen (Application.h, StartupProgress). Its first call comes
// AFTER the user's theme is active and not before, so a screen that draws
// itself on that call draws in the right theme.
static void initializeServices(
const std::function<void(std::string_view status, double fraction)>& reportStep = {});
// ---- the wall (A3.1) --------------------------------------------------
//
// Resolves the licence ONCE, binds it so `icore::License` and every
// ICoreFeatureGate can answer, and hands the entitlements to the update
// service. Returns whether this session may open an editor window.
//
// ⚠ THIS FUNCTION NO LONGER SHOWS A DIALOG (owner, 2026-08-24). It used to
// raise the wall itself, parentless, before any window existed. The
// instruction is that THE PRIMARY WINDOW ALWAYS OPENS AND THE WALL ARRIVES
// ON TOP OF IT, so the dialog is showSignInWallIfUnlicensed()'s, called
// once the window is on screen. What did NOT move is the enforcing half:
// the gate is bound here, before any window is constructed, so
// `icore::License` and every ICoreFeatureGate already answer Unlicensed by
// the time one exists. The wall was never what refused a feature.
//
// ⚠ A WINDOWED SESSION ALWAYS MAY, SINCE A3.5 (2026-08-24). Resolving
// nothing no longer ends the process -- the session opens in no-licence
// mode instead. The gate stays Unlicensed
// and stays bound, so nothing is granted: every paid feature is refused by
// ICoreFeatureGate off the empty entitlements, and ICoreLicenseBanner shows
// its non-dismissible Unlicensed row. Only a HEADLESS session can still be
// refused, and it must stay that way -- a --console run that proceeded
// unlicensed would report passes for features the gates had refused, so CI
// would go quiet instead of red.
//
// ⚠ HEADLESS IS A DIFFERENT SHAPE, NOT A WEAKER ONE. A `--console` session
// resolves the same way through the same verifier, but over an IN-MEMORY
// vault: the OS keychain's first read from a freshly relinked binary raises
// an authorisation prompt that a headless run waits on FOREVER (measured in
// this tree, in the boundaries suite), and `resolveOnLaunch()` also WRITES
// the clock anchor. Handing the headless gate a memory vault makes the
// keychain unreachable by construction rather than by care, and costs
// nothing real: an environment token is never persisted anyway (A3.2), so
// there was never anything in the vault for CI to read. The escape hatch is
// ICORE_LICENSE_TOKEN, verified identically — never a bypass.
//
// ⚠ AND IT NEVER RAISES A DIALOG HEADLESS. A wall with no one to click it
// is a hang, and a hang is how this fails for everybody's suites at once
// (ACCOUNT_MANAGER N3). Unlicensed headless is reported by the caller as a
// message and a non-zero exit; only a windowed session shows the dialog.
//
// Idempotent: the first call resolves, the rest report what it decided.
static bool resolveLicense();
// ---- Usage telemetry (ACCOUNT_MANAGER A6.6) -------------------------
//
// ICoreTelemetrySession was built and tested at A6.1-A6.5 and then never
// constructed, so for six weeks no session left any copy of the app. These
// three are the shell's half: resolveLicense() starts it once the gate is
// bound, the shell's timer ticks it, and the quit path ends it.
//
// ⚠ NEVER IN A --console SESSION. The suites run inside the real
// application; a session started there would file every regression run
// as a customer using the product.
//
// ⚠ OPT-OUT IS READ BEFORE ANYTHING IS SENT. startTelemetry() binds the
// preference (which pushes the user's saved choice into the session)
// before it calls start(), so an opted-out user sends nothing at all.
static void startTelemetry();
// Every few seconds from the shell's timer. Samples whether one of the
// app's windows is in front and whether the pointer moved -- the activity
// signal ICoreActiveTime counts against its five-minute idle rule -- then
// lets the session send whatever heartbeat or flush is due. Cheap when
// nothing is due; a no-op when telemetry never started.
static void tickTelemetry();
// The clean_exit row. Fire-and-forget: the app never waits for telemetry
// on the way out, and a row that does not arrive is reaped server-side
// (close_stale_sessions) with at most one heartbeat of active time lost.
static void endTelemetry();
// The wall itself, raised ONCE, over a window that is already on screen.
//
// Does nothing at all unless every one of these holds: this is a windowed
// session, resolveLicense() has run, it resolved to Unlicensed, and no
// startup wall has been raised yet. So the licensed case costs a comparison
// and a console session can never reach a dialog.
//
// ⚠ CALL IT AFTER THE PRIMARY WINDOW IS SHOWN, AND AFTER
// applyStartupBehavior(). That ordering is the owner's instruction of
// 2026-08-24 -- the primary window opens whatever the licence says, and the
// wall arrives on top of it -- and it is also what puts the dialog in
// front: whichever window goes up last is the one the window system leaves
// there. Both entry paths do this: initializeApp() as its last statement,
// and icore::Application through installStartupBehaviorHook().
//
// ⚠ IT GOES THROUGH ICorePrimaryWindow::raiseSignInWall() WHEN THERE IS A
// PRIMARY WINDOW, and that is not an implementation detail:
// ICoreLicenseBanner is built during the window's construction, which now
// happens while the session is still Unlicensed, so it comes up saying so
// and something must refresh it when the sign-in succeeds. The window's own
// path is the one place that does -- and it holds the one-wall-at-a-time
// guard that keeps this from stacking with the wall a sign-out raises.
//
// Cancelling does not quit (A3.5). A REFUSAL still explains itself
// afterwards (A5.5); a cancellation deliberately does not.
static void showSignInWallIfUnlicensed();
// Whether this session is allowed to put an editor window on screen.
// Safe before resolveLicense() runs (answers false) and after.
//
// ⚠ THIS IS NOT "IS LICENSED", AND THE OLD NAME SAID IT WAS. Since A3.5 a
// windowed session that resolves nothing opens anyway in no-licence mode,
// so this answers TRUE while the gate is Unlicensed and ICoreFeatureGate
// refuses simulation, code generation and every other paid feature. Only a
// headless session can answer false now. ⚠ If your question is "may the
// user do X", ask ICoreFeatureGate or icore::License -- a caller that
// reaches for this one is asking whether a WINDOW is allowed to exist.
[[nodiscard]] static bool sessionMayOpenAnEditor();
// Whether this process was launched with --console. Recorded by
// configureApplication(), which is the first thing holding the argument
// vector; both entry paths call it.
[[nodiscard]] static bool isConsoleSession();
// The sentence a refused headless session prints. Public so the console
// dispatch can print it and a suite can assert it names the variable and
// the diagnostic command rather than saying "unlicensed".
[[nodiscard]] static std::string headlessRefusalMessage();
// Console commands a refused session may still run. `licenseStatus` has to
// work on the machine where verification is FAILING -- that is the whole
// reason it exists -- so the wall must not be what stops you diagnosing the
// wall.
[[nodiscard]] static bool isLicenseDiagnosticCommand(const std::string& commandLine);
// initializeServices(), then THE primary window -- created, shown, and
// handed to the startup behavior. This is Main.cpp's entry point and the
// reason initializeServices() exists separately: the SDK wants the first
// half without the second.
static void initializeApp();
// Opens what the user launches into, once the primary window exists. Nothing
// else loads a project at startup, so this always ends with either a project
// open or the launcher up: it acts on the startup preference (Settings ->
// Startup) to either reopen the most recent project or go straight to the
// launcher, and falls back to the New Project form when there is no recent
// project to reopen or the most recent one cannot be read.
//
// Public rather than a private step of initializeApp(), because there are
// now two ways the primary window comes into being and only one of them is
// initializeApp(). icore::Application creates no window -- the SDK caller
// does, through createEditorWindow() -- so the SDK arms this on the event
// loop and it fires once that window is up. See src/ICoreBlocks/Shell/Application.cpp.
//
// @pre A primary window exists and is on screen. It is the parent of the
// prompts raised here, and the window the launcher has to land in
// front of.
//
// ⚠ EXCEPT WHEN A STARTUP SCREEN IS UP. Then the window exists but is
// still hidden behind it, and the two callbacks are how the caller keeps
// the rest of that precondition true:
// * `beforeOpening(name)` runs just before the most recent project is
// reopened -- the step a startup screen names ("Opening <name>").
// * `beforeLauncher()` runs just before the launcher is raised, so the
// caller can put the primary window on screen FIRST and the launcher
// still lands in front of it.
// Both are optional; with neither, this is exactly the old behaviour.
static void applyStartupBehavior(
const std::function<void(const std::string& projectName)>& beforeOpening = {},
const std::function<void()>& beforeLauncher = {});
// --- who the primary window is -----------------------------------------
//
// Two paths reach this: initializeApp(), which creates the window itself,
// and icore::EditorWindow's Impl, which is what creates it on the SDK path.
// Registering from both is what keeps getPrimaryWindow() -- and the dialog
// parent applyStartupBehavior() uses -- answering the same question no
// matter which path built the app.
//
// First non-null registration wins: an Application may hand out more than
// one editor window, and the app's primary window is the first of them.
static void setPrimaryWindow(ICorePrimaryWindow* window);
// Withdraws `window` if it is the one currently registered, and does
// nothing otherwise. Called from the window's own destructor, so a second
// editor window closing can never blank a pointer that belongs to the
// first.
static void clearPrimaryWindow(const ICorePrimaryWindow* window);
static ICorePrimaryWindow* getPrimaryWindow();
};
};