docs(c++): OriginManager singleton API reference

Document the OriginManager singleton: origin registration/unregistration
lifecycle, document-to-origin resolution algorithm (non-local checked
first, local as fallback), explicit vs detected ownership, preference
persistence, signal catalog, and memory model.

Closes #132
This commit is contained in:
2026-02-10 07:55:46 -06:00
parent bdbe1b163a
commit 98218661d0
2 changed files with 227 additions and 0 deletions

View File

@@ -50,3 +50,4 @@
# C++ API Reference
- [FileOrigin Interface](./reference/cpp-file-origin.md)
- [OriginManager](./reference/cpp-origin-manager.md)

View File

@@ -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<std::string>`
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<string, std::unique_ptr<FileOrigin>>
│ ├─ "local" → LocalFileOrigin (always present)
│ ├─ "silo" → FileOriginPython (wraps Python SiloOrigin)
│ └─ ... → other registered origins
├─ _currentOriginId: std::string
└─ _documentOrigins: std::map<App::Document*, string>
├─ 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