diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index c0517b095f..b7f378778e 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -50,3 +50,4 @@ # C++ API Reference - [FileOrigin Interface](./reference/cpp-file-origin.md) +- [OriginManager](./reference/cpp-origin-manager.md) diff --git a/docs/src/reference/cpp-origin-manager.md b/docs/src/reference/cpp-origin-manager.md new file mode 100644 index 0000000000..4283cd2d2f --- /dev/null +++ b/docs/src/reference/cpp-origin-manager.md @@ -0,0 +1,226 @@ +# OriginManager — Singleton Registry + +> **Header:** `src/Gui/OriginManager.h` +> **Implementation:** `src/Gui/OriginManager.cpp` +> **Namespace:** `Gui` + +`OriginManager` is the central singleton that tracks all registered +[FileOrigin](./cpp-file-origin.md) instances, maintains the user's current +origin selection, and resolves which origin owns a given document. + +## Lifecycle + +``` +Application startup + │ + ├─ OriginManager::instance() ← singleton created + │ ├─ ensureLocalOrigin() ← "local" origin always exists + │ └─ loadPreferences() ← restore last-used origin from prefs + │ + ├─ Python addons register origins ← e.g. SiloOrigin via FreeCADGui.addOrigin() + │ + ├─ ... application runs ... + │ + └─ OriginManager::destruct() ← saves prefs, deletes all origins +``` + +The `"local"` origin is created in the constructor and **cannot be +unregistered**. It serves as the universal fallback. + +## Singleton Access + +```cpp +// Get the instance (created on first call) +OriginManager* mgr = OriginManager::instance(); + +// Destroy at application shutdown +OriginManager::destruct(); +``` + +## Origin Registration + +### `registerOrigin(FileOrigin* origin) → bool` + +Register a new origin. The manager takes **ownership** via +`std::unique_ptr` — do not delete the pointer after registration. + +- Returns `false` and deletes the origin if: + - `origin` is `nullptr` + - `origin->id()` is empty + - An origin with the same ID is already registered +- Emits `signalOriginRegistered(id)` on success. + +```cpp +auto* cloud = new MyCloudOrigin(); +bool ok = OriginManager::instance()->registerOrigin(cloud); +// cloud is now owned by OriginManager — do not delete it +``` + +### `unregisterOrigin(const std::string& id) → bool` + +Remove and delete an origin by ID. + +- Returns `false` if: + - `id` is `"local"` (built-in, cannot be removed) + - No origin with that ID exists +- If the unregistered origin was the current origin, the current origin + automatically reverts to `"local"`. +- Emits `signalOriginUnregistered(id)` on success. + +### `getOrigin(const std::string& id) → FileOrigin*` + +Look up an origin by ID. Returns `nullptr` if not found. + +### `originIds() → std::vector` + +Returns all registered origin IDs. Order is the `std::map` key order +(alphabetical). + +## Current Origin Selection + +The "current origin" determines which backend is used for **new +documents** (File > New) and appears selected in the +[OriginSelectorWidget](./cpp-origin-selector-widget.md) toolbar dropdown. + +### `currentOrigin() → FileOrigin*` + +Returns the currently selected origin. Never returns `nullptr` — falls +back to `"local"` if the stored ID is somehow invalid. + +### `currentOriginId() → std::string` + +Returns the current origin's ID string. + +### `setCurrentOrigin(const std::string& id) → bool` + +Switch the current origin. Returns `false` if the ID is not registered. + +- Persists the selection to FreeCAD preferences at + `User parameter:BaseApp/Preferences/General/Origin/CurrentOriginId`. +- Emits `signalCurrentOriginChanged(id)` when the selection actually + changes (no signal if setting to the already-current origin). + +### `originForNewDocument() → FileOrigin*` + +Convenience method — returns `currentOrigin()`. Called by the File > New +command to determine which origin should handle new document creation. + +## Document-Origin Resolution + +When FreeCAD needs to know which origin owns a document (e.g., for +File > Save), it uses the resolution chain below. + +### `originForDocument(App::Document* doc) → FileOrigin*` + +Primary lookup method. Resolution order: + +1. **Explicit association** — check the `_documentOrigins` cache for a + prior `setDocumentOrigin()` call. +2. **Ownership detection** — call `findOwningOrigin(doc)` to scan all + origins. +3. **Cache the result** — store in `_documentOrigins` for future lookups. + +Returns `nullptr` only if no origin claims the document (should not +happen in practice since `LocalFileOrigin` is the universal fallback). + +### `findOwningOrigin(App::Document* doc) → FileOrigin*` + +Scans all registered origins by calling `ownsDocument(doc)` on each. + +**Algorithm:** + +``` +1. For each origin where id ≠ "local": + if origin->ownsDocument(doc): + return origin ← PLM/Cloud origin claims it + +2. if localOrigin->ownsDocument(doc): + return localOrigin ← fallback to local + +3. return nullptr ← no owner (shouldn't happen) +``` + +Non-local origins are checked **first** because they have specific +ownership criteria (e.g., presence of `SiloItemId` property). The local +origin uses negative-match logic (owns anything not claimed by others), +so it must be checked last to avoid false positives. + +### `setDocumentOrigin(doc, origin)` + +Explicitly associate a document with an origin. Used when creating new +documents to mark them with the origin that created them. + +- Passing `origin = nullptr` clears the association. +- Emits `signalDocumentOriginChanged(doc, originId)`. + +### `clearDocumentOrigin(doc)` + +Remove a document from the association cache. Called when a document is +closed to prevent stale pointers. + +## Signals + +All signals use the [fastsignals](https://github.com/nicktrandafil/fastsignals) +library (not Qt signals). + +| Signal | Parameters | When | +|--------|-----------|------| +| `signalOriginRegistered` | `const std::string& id` | After a new origin is registered | +| `signalOriginUnregistered` | `const std::string& id` | After an origin is removed | +| `signalCurrentOriginChanged` | `const std::string& id` | After the user switches the current origin | +| `signalDocumentOriginChanged` | `App::Document*, const std::string& id` | After a document-origin association changes | + +### Subscribers + +- **[OriginSelectorWidget](./cpp-origin-selector-widget.md)** subscribes + to `signalOriginRegistered`, `signalOriginUnregistered`, and + `signalCurrentOriginChanged` to rebuild the dropdown menu and update + the toolbar button. +- **[CommandOrigin](./cpp-command-origin.md)** commands query the manager + on each `isActive()` call to check document ownership and origin + capabilities. + +## Preference Persistence + +| Path | Key | Type | Default | +|------|-----|------|---------| +| `User parameter:BaseApp/Preferences/General/Origin` | `CurrentOriginId` | ASCII string | `"local"` | + +Loaded in the constructor, saved on destruction and on each +`setCurrentOrigin()` call. If the saved origin ID is not registered at +load time (e.g., the Silo addon hasn't loaded yet), the manager falls +back to `"local"` and will re-check when origins are registered later. + +## Memory Model + +``` +OriginManager (singleton) + │ + ├─ _origins: std::map> + │ ├─ "local" → LocalFileOrigin (always present) + │ ├─ "silo" → FileOriginPython (wraps Python SiloOrigin) + │ └─ ... → other registered origins + │ + ├─ _currentOriginId: std::string + │ + └─ _documentOrigins: std::map + ├─ doc1 → "silo" + ├─ doc2 → "local" + └─ ... (cache, cleared on document close) +``` + +- Origins are owned exclusively by the manager via `unique_ptr`. +- The document-origin map uses raw `App::Document*` pointers. Callers + **must** call `clearDocumentOrigin()` when a document is destroyed to + prevent dangling pointers. + +## See Also + +- [FileOrigin Interface](./cpp-file-origin.md) — the abstract interface + that registered origins implement +- [FileOriginPython](./cpp-file-origin-python.md) — bridge for + Python-implemented origins +- [CommandOrigin](./cpp-command-origin.md) — commands that dispatch to + origins via this manager +- [OriginSelectorWidget](./cpp-origin-selector-widget.md) — toolbar UI + driven by this manager's signals